Initial Contribution
diff --git a/core/java/android/accounts/AccountMonitor.java b/core/java/android/accounts/AccountMonitor.java
new file mode 100644
index 0000000..9bcc1e7
--- /dev/null
+++ b/core/java/android/accounts/AccountMonitor.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+    // 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;
+            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;
+            }
+            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 an intent 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 void notifyListener() {
+        // 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 the accounts service (Missing service?)");
+        }
+    }
+
+    /**
+     * 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..fa310a5
--- /dev/null
+++ b/core/java/android/app/Activity.java
@@ -0,0 +1,3419 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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;
+
+/**
+ * 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
+ * <a href="{@docRoot}intro/lifecycle.html">application's overall lifecycle</a>,
+ * and the way activities are launched and put together is a fundamental
+ * part of the platform's
+ * <a href="{@docRoot}intro/appmodel.html">application model</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}devel/security.html">Security Model</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;
+    private Object mLastNonConfigurationInstance;
+    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;
+
+    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) {
+        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} or {@link #onStop} when the current
+     * activity is now being displayed to the user.  It will
+     * be followed by {@link #onRestart}.
+     *
+     * <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 #onStart} when the current activity is being
+     * re-displayed to the user (the user has navigated back to it).  It will
+     * be followed by {@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 #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;
+    }
+
+    /**
+     * 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 #onStart}, {@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;
+    }
+    
+    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 #managedCommitUpdates
+     * @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 #managedCommitUpdates
+     * @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;
+    }
+    
+    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.
+     *
+     * @param hasFocus Whether the window of this activity has focus.
+     */
+    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 (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 (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) {
+        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();
+    }
+    
+    /**
+     * 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;
+        }
+    }
+
+    /**
+     * 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},
+     * 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) {
+        attachBaseContext(context);
+
+        mWindow = PolicyManager.makeNewWindow(this);
+        mWindow.setCallback(this);
+        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;
+
+        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 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..96bb475
--- /dev/null
+++ b/core/java/android/app/ActivityGroup.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.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A screen that contains and runs multiple embedded activities.
+ */
+public class ActivityGroup extends Activity {
+    private static final String STATES_KEY = "android:states";
+
+    /**
+     * 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());
+    }
+
+    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..6eb1102
--- /dev/null
+++ b/core/java/android/app/ActivityManager.java
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.IPackageDataObserver;
+import android.graphics.Bitmap;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Parcelable.Creator;
+import android.text.TextUtils;
+import android.util.Log;
+
+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;
+        }
+    }
+}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
new file mode 100644
index 0000000..e6f1b05
--- /dev/null
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -0,0 +1,2054 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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 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;
+        }
+        
+        }
+        
+        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 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 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 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();
+    }
+    
+    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..d03a76f
--- /dev/null
+++ b/core/java/android/app/ActivityThread.java
@@ -0,0 +1,3754 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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() {
+        if (mDisplay == null) {
+            WindowManager wm = WindowManagerImpl.getDefault();
+            mDisplay = wm.getDefaultDisplay();
+        }
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        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();
+            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.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());
+                    //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;
+        boolean paused;
+        boolean stopped;
+        boolean hideForNow;
+        Configuration newConfig;
+        ActivityRecord nextIdle;
+
+        ActivityInfo activityInfo;
+        PackageInfo packageInfo;
+
+        List<ResultInfo> pendingResults;
+        List<Intent> pendingIntents;
+
+        boolean startsNotResumed;
+
+        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";
+
+        public final void schedulePauseActivity(IBinder token, boolean finished,
+                int configChanges) {
+            queueOrSendMessage(
+                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
+                    token, 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) {
+            queueOrSendMessage(H.RESUME_ACTIVITY, token);
+        }
+
+        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) {
+            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;
+
+            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;
+
+            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:", ViewDebug.getViewInstanceCount(), "ViewRoots:",
+                    ViewDebug.getViewRootInstanceCount());
+
+            printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", ApplicationContext.getInstanceCount(),
+                    "Activities:", Activity.getInstanceCount());
+
+            printRow(pw, TWO_COUNT_COLUMNS, "Assets:", AssetManager.getGlobalAssetCount(),
+                    "AssetManagers:", AssetManager.getGlobalAssetManagerCount());
+
+            printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", Debug.getBinderLocalObjectCount(),
+                    "Proxy Binders:", Debug.getBinderProxyObjectCount());
+            printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", Debug.getBinderDeathObjectCount());
+
+            printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", OpenSSLSocketImpl.getInstanceCount());
+
+            // SQLite mem info
+            long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
+            SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
+            SQLiteDebug.getPagerStats(stats);
+
+            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.arg2);
+                    break;
+                case PAUSE_ACTIVITY_FINISHING:
+                    handlePauseActivity((IBinder)msg.obj, true, 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);
+                    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;
+    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());
+                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) {
+        ActivityRecord r = new ActivityRecord();
+            r.token = token;
+            r.intent = intent;
+            r.state = state;
+            r.parent = parent;
+            r.embeddedID = id;
+            r.activityInfo = activityInfo;
+        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, config);
+                
+                r.lastNonConfigurationInstance = 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);
+
+            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) {
+        // 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);
+
+            // 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;
+                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;
+                }
+                r.activity.mDecor.setVisibility(View.VISIBLE);
+                mNumVisibleActivities++;
+            }
+
+            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,
+            int configChanges) {
+        ActivityRecord r = mActivities.get(token);
+        if (r != null) {
+            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 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 (v.getVisibility() != View.VISIBLE) {
+                    v.setVisibility(View.VISIBLE);
+                    mNumVisibleActivities++;
+                }
+                if (r.newConfig != null) {
+                    performConfigurationChanged(r.activity, r.newConfig);
+                    r.newConfig = null;
+                }
+            } else {
+                if (v.getVisibility() == View.VISIBLE) {
+                    v.setVisibility(View.INVISIBLE);
+                    mNumVisibleActivities--;
+                }
+            }
+        }
+    }
+
+    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.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 (v.getVisibility() == View.VISIBLE) {
+                    mNumVisibleActivities--;
+                }
+                IBinder wtoken = v.getWindowToken();
+                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);
+
+            // set it for java, this also affects newly created Resources
+            if (config.locale != null) {
+                Locale.setDefault(config.locale);
+            }
+
+            if (mSystemContext != null) {
+                mSystemContext.getResources().updateConfiguration(config, null);
+                //Log.i(TAG, "Updated system resources " + mSystemContext.getResources()
+                //        + ": " + mSystemContext.getResources().getConfiguration());
+            }
+
+            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, null);
+                        //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..35c6ac1
--- /dev/null
+++ b/core/java/android/app/AlarmManager.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static IAlarmManager mService;
+
+    static {
+        mService = IAlarmManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ALARM_SERVICE));
+    }
+    
+    /**
+     * package private on purpose
+     */
+    AlarmManager() {
+    }
+    
+    /**
+     * 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 an intent 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) {
+        }
+    }
+
+    /**
+     * 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..cc80ba4
--- /dev/null
+++ b/core/java/android/app/AlertDialog.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.widget.AdapterView;
+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>
+ */
+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());
+    }
+    
+    @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);
+    }
+
+    public void setButton(CharSequence text, Message msg) {
+        mAlert.setButton(text, msg);
+    }
+
+    public void setButton2(CharSequence text, Message msg) {
+        mAlert.setButton2(text, msg);
+    }
+
+    public void setButton3(CharSequence text, Message msg) {
+        mAlert.setButton3(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.
+     */
+    public void setButton(CharSequence text, final OnClickListener listener) {
+        mAlert.setButton(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.
+     */
+    public void setButton2(CharSequence text, final OnClickListener listener) {
+        mAlert.setButton2(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.
+     */
+    public void setButton3(CharSequence text, final OnClickListener listener) {
+        mAlert.setButton3(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.
+         */
+        public Builder setTitle(int titleId) {
+            P.mTitle = P.mContext.getText(titleId);
+            return this;
+        }
+        
+        /**
+         * Set the title displayed in the {@link Dialog}.
+         */
+        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.
+         */
+        public Builder setCustomTitle(View customTitleView) {
+            P.mCustomTitleView = customTitleView;
+            return this;
+        }
+        
+        /**
+         * Set the message to display using the given resource id.
+         */
+        public Builder setMessage(int messageId) {
+            P.mMessage = P.mContext.getText(messageId);
+            return this;
+        }
+        
+        /**
+         * Set the message to display.
+         */
+        public Builder setMessage(CharSequence message) {
+            P.mMessage = message;
+            return this;
+        }
+        
+        /**
+         * Set the resource id of the {@link Drawable} to be used in the title.
+         */
+        public Builder setIcon(int iconId) {
+            P.mIconId = iconId;
+            return this;
+        }
+        
+        /**
+         * Set the {@link Drawable} to be used in the title.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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)
+         */
+        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.
+         */
+        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
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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.
+         */
+        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)
+         */
+        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.
+         */
+        public Builder setView(View view) {
+            P.mView = view;
+            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 sets.
+         */
+        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..342ffcf
--- /dev/null
+++ b/core/java/android/app/ApplicationContext.java
@@ -0,0 +1,2596 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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;
+
+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.
+ */
+@SuppressWarnings({"EmptyCatchBlock"})
+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 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");
+    }
+
+    @Override
+    public SharedPreferences getSharedPreferences(String name, int mode) {
+        File f;
+        f = makeFilename(getPreferencesDir(), name + ".xml");
+        SharedPreferencesImpl sp;
+        synchronized (sSharedPrefs) {
+            sp = sSharedPrefs.get(f);
+            if (sp != null && !sp.hasFileChanged()) {
+                //Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
+                return sp;
+            }
+        }
+
+        Map map = null;
+        try {
+            FileInputStream str = new FileInputStream(f);
+            map = XmlUtils.readMapXml(str);
+            str.close();
+        } catch (org.xmlpull.v1.XmlPullParserException e) {
+        } catch (java.io.FileNotFoundException e) {
+        } catch (java.io.IOException 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.toString(), mode, 0);
+            return fos;
+        } catch (FileNotFoundException e) {
+        }
+
+        File parent = f.getParentFile();
+        parent.mkdir();
+        FileUtils.setPermissions(
+            parent.toString(),
+            FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+            -1, -1);
+        FileOutputStream fos = new FileOutputStream(f, append);
+        setFilePermissionsFromMode(f.toString(), 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");
+            }
+            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.toString(),
+                        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.toString(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                -1, -1);
+        }
+
+        File f = makeFilename(dir, name);
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory);
+        setFilePermissionsFromMode(f.toString(), 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 new AlarmManager();
+        } 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();
+        }
+
+        return null;
+    }
+
+    private ActivityManager getActivityManager() {
+        synchronized (mSync) {
+            if (mActivityManager == null) {
+                mActivityManager = new ActivityManager(getOuterContext(),
+                        mMainThread.getHandler());
+            }
+        }
+        return mActivityManager;
+    }
+
+    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.toString(), 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);
+        }
+
+        @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 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 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.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));
+        }
+
+        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 freeApplicationCache(long idealStorageSize, 
+                IPackageDataObserver observer) {
+            try {
+                mPM.freeApplicationCache(idealStorageSize, observer);
+            } 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 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;
+            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 boolean writeFileLocked() {
+            try {
+                FileOutputStream str;
+                try {
+                    str = new FileOutputStream(mFile);
+                } catch (Exception e) {
+                    File parent = mFile.getParentFile();
+                    parent.mkdir();
+                    FileUtils.setPermissions(
+                        parent.toString(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+                    str = new FileOutputStream(mFile);
+                }
+                XmlUtils.writeMapXml(mMap, str);
+                str.close();
+                setFilePermissionsFromMode(mFile.toString(), mMode, 0);
+                if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
+                    mTimestamp = mFileStatus.mtime;
+                }
+            } catch (org.xmlpull.v1.XmlPullParserException e) {
+            } catch (java.io.FileNotFoundException e) {
+            } catch (java.io.IOException e) {
+            }
+            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..6a70329
--- /dev/null
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+            int configChanges = data.readInt();
+            schedulePauseActivity(b, finished, 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();
+            scheduleResumeActivity(b);
+            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;
+            scheduleLaunchActivity(intent, b, info, state, ri, pi, notResumed);
+            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,
+            int configChanges) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        data.writeInt(finished ? 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)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(token);
+        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)
+    		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);
+        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..7450559
--- /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.pim.DateFormat;
+import android.text.TextUtils.TruncateAt;
+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.getLongDateFormat(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..f1d2e65
--- /dev/null
+++ b/core/java/android/app/Dialog.java
@@ -0,0 +1,935 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+ * 
+ * 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.
+ * 
+ */
+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}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 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();
+        mWindowManager.addView(mDecor, mWindow.getAttributes());
+        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.getWindowTouchSlop();
+        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..75dfcae
--- /dev/null
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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
+ *         android:orientation=&quot;vertical&quot;
+ *         android:layout_width=&quot;fill_parent&quot; 
+ *         android:layout_height=&quot;fill_parent&quot;
+ *         android:paddingLeft=&quot;8&quot;
+ *         android:paddingRight=&quot;8&quot;&gt;
+ * 
+ *     &lt;ExpandableListView id=&quot;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;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
+ *     android:layout_width=&quot;fill_parent&quot;
+ *     android:layout_height=&quot;wrap_content&quot;
+ *     android:orientation=&quot;vertical&quot;&gt;
+ * 
+ *     &lt;TextView id=&quot;text1&quot;
+ *         android:textSize=&quot;16&quot;
+ *         android:textStyle=&quot;bold&quot;
+ *         android:layout_width=&quot;fill_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * 
+ *     &lt;TextView id=&quot;text2&quot;
+ *         android:textSize=&quot;16&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 a {@link AdapterContextMenuInfo} whose position 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..2de21ed
--- /dev/null
+++ b/core/java/android/app/IActivityManager.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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;
+    
+    /** 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;
+}
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..c7f20b9b
--- /dev/null
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -0,0 +1,33 @@
+/* //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 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..ecd993a2
--- /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,
+            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) 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)
+    		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..f80c947
--- /dev/null
+++ b/core/java/android/app/Instrumentation.java
@@ -0,0 +1,1601 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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.onPause();
+    }
+    
+    /*
+     * 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 whoThread 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, IApplicationThread whoThread, IBinder token, Activity target,
+        Intent intent, int requestCode) {
+        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/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..8f0a4f5
--- /dev/null
+++ b/core/java/android/app/LauncherActivity.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+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 {
+    
+    /**
+     * 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<ResolveInfo> mOriginalValues;
+
+        protected final Context mContext;
+        protected final Intent mIntent;
+        protected final LayoutInflater mInflater;
+
+        protected List<ResolveInfo> mActivitiesList;
+
+        private Filter mFilter;
+
+        public ActivityAdapter(Context context, Intent intent) {
+            mContext = context;
+            mIntent = new Intent(intent);
+            mIntent.setComponent(null);
+            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+            PackageManager pm = context.getPackageManager();
+            mActivitiesList = pm.queryIntentActivities(intent, 0);
+            if (mActivitiesList != null) {
+                Collections.sort(mActivitiesList, new ResolveInfo.DisplayNameComparator(pm));
+            }
+        }
+
+        public Intent intentForPosition(int position) {
+            if (mActivitiesList == null) {
+                return null;
+            }
+
+            Intent intent = new Intent(mIntent);
+            ActivityInfo ai = mActivitiesList.get(position).activityInfo;
+            intent.setClassName(ai.applicationInfo.packageName, ai.name);
+            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.simple_list_item_1, parent, false);
+            } else {
+                view = convertView;
+            }
+            bindView(view, mActivitiesList.get(position));
+            return view;
+        }
+
+        private char getCandidateLetter(ResolveInfo info) {
+            PackageManager pm = mContext.getPackageManager();
+            CharSequence label = info.loadLabel(pm);
+
+            if (label == null) {
+                label = info.activityInfo.name;
+            }
+
+            return Character.toLowerCase(label.charAt(0));
+        }
+
+        private void bindView(View view, ResolveInfo info) {
+            TextView text = (TextView) view.findViewById(com.android.internal.R.id.text1);
+
+            PackageManager pm = mContext.getPackageManager();
+            CharSequence label = info.loadLabel(pm);
+            text.setText(label != null ? label : info.activityInfo.name);
+        }
+
+        public Filter getFilter() {
+            if (mFilter == null) {
+                mFilter = new ArrayFilter();
+            }
+            return mFilter;
+        }
+
+        /**
+         * <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 ArrayFilter extends Filter {
+            @Override
+            protected FilterResults performFiltering(CharSequence prefix) {
+                FilterResults results = new FilterResults();
+
+                if (mOriginalValues == null) {
+                    synchronized (lock) {
+                        mOriginalValues = new ArrayList<ResolveInfo>(mActivitiesList);
+                    }
+                }
+
+                if (prefix == null || prefix.length() == 0) {
+                    synchronized (lock) {
+                        ArrayList<ResolveInfo> list = new ArrayList<ResolveInfo>(mOriginalValues);
+                        results.values = list;
+                        results.count = list.size();
+                    }
+                } else {
+                    final PackageManager pm = mContext.getPackageManager();
+                    final String prefixString = prefix.toString().toLowerCase();
+
+                    ArrayList<ResolveInfo> values = mOriginalValues;
+                    int count = values.size();
+
+                    ArrayList<ResolveInfo> newValues = new ArrayList<ResolveInfo>(count);
+
+                    for (int i = 0; i < count; i++) {
+                        ResolveInfo value = values.get(i);
+
+                        final CharSequence label = value.loadLabel(pm);
+                        final CharSequence name = label != null ? label : value.activityInfo.name;
+
+                        String[] words = name.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(value);
+                                break;
+                            }
+                        }
+                    }
+
+                    results.values = newValues;
+                    results.count = newValues.size();
+                }
+
+                return results;
+            }
+
+            @Override
+            protected void publishResults(CharSequence constraint, FilterResults results) {
+                //noinspection unchecked
+                mActivitiesList = (List<ResolveInfo>) results.values;
+                if (results.count > 0) {
+                    notifyDataSetChanged();
+                } else {
+                    notifyDataSetInvalidated();
+                }
+            }
+        }
+    }
+    
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        mAdapter = new ActivityAdapter(this, getTargetIntent());
+        
+        setListAdapter(mAdapter);
+        getListView().setTextFilterEnabled(true);
+    }
+
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Intent intent = ((ActivityAdapter)mAdapter).intentForPosition(position);
+
+        startActivity(intent);
+    }
+    
+    protected abstract Intent getTargetIntent();
+   
+}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
new file mode 100644
index 0000000..2818937
--- /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
+ *         android:orientation=&quot;vertical&quot;
+ *         android:layout_width=&quot;fill_parent&quot; 
+ *         android:layout_height=&quot;fill_parent&quot;
+ *         android:paddingLeft=&quot;8&quot;
+ *         android:paddingRight=&quot;8&quot;&gt;
+ * 
+ *     &lt;ListView id=&quot;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;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
+ *     android:layout_width=&quot;fill_parent&quot;
+ *     android:layout_height=&quot;wrap_content&quot;
+ *     android:orientation=&quot;vertical&quot;&gt;
+ * 
+ *     &lt;TextView id=&quot;text1&quot;
+ *         android:textSize=&quot;16&quot;
+ *         android:textStyle=&quot;bold&quot;
+ *         android:layout_width=&quot;fill_parent&quot;
+ *         android:layout_height=&quot;wrap_content&quot;/&gt;
+ * 
+ *     &lt;TextView id=&quot;text2&quot;
+ *         android:textSize=&quot;16&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 icicle){
+ *         super.onCreate(icicle);
+ * 
+ *         // 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..12e70e3
--- /dev/null
+++ b/core/java/android/app/LocalActivityManager.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+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) {
+            // 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);
+            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);
+        }
+    }
+
+    /**
+     * 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..cc56385
--- /dev/null
+++ b/core/java/android/app/Notification.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.pim.DateFormat;
+import android.pim.DateUtils;
+import android.text.TextUtils;
+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
+     * <a href="{@docRoot}intro/appmodel.html">application model</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 the
+     * <a href="{@docRoot}intro/appmodel.html">application model</a> document.
+     */
+    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..afb3827
--- /dev/null
+++ b/core/java/android/app/NotificationManager.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.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.  If you
+     *        pass null, there will be no persistent notification and no
+     *        flashing, vibration, etc.
+     */
+    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..ba84903
--- /dev/null
+++ b/core/java/android/app/PendingIntent.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     */
+    public static final int FLAG_CANCEL_CURRENT = 1<<28;
+
+    /**
+     * 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}, 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}, 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}, 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 == null) {
+            return false;
+        }
+        try {
+            return mTarget.asBinder().equals(((PendingIntent)otherObj)
+                    .mTarget.asBinder());
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    @Override
+    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<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..8b60cfa
--- /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 (mHasStarted && (isIndeterminate() != indeterminate)) {
+            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..5f3f9ef
--- /dev/null
+++ b/core/java/android/app/SearchDialog.java
@@ -0,0 +1,1606 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.server.search.SearchableInfo;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.View.OnFocusChangeListener;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.CursorAdapter;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+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.util.concurrent.LinkedBlockingQueue;
+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 {
+
+    // 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 final Handler mHandler = new Handler(); // why isn't Dialog.mHandler shared?
+    
+    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 View mSearchBarLayout;
+    private TextView mBadgeLabel;
+    private LinearLayout mSearchEditLayout;
+    private EditText mSearchTextField;
+    private Button mGoButton;
+    private ListView mSuggestionsList;
+
+    private ViewTreeObserver mViewTreeObserver = null;
+    
+    // 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 SuggestionsRunner mSuggestionsRunner;
+    private String mUserQuery = null;
+    private int mUserQuerySelStart;
+    private int mUserQuerySelEnd;
+    private boolean mNonUserQuery = false;
+    private boolean mLeaveJammedQueryOnRefocus = false;
+    private String mPreviousSuggestionQuery = null;
+    private Context mProviderContext;
+    private Animation mSuggestionsEntry;
+    private Animation mSuggestionsExit;
+    private boolean mSkipNextAnimate;
+    private int mPresetSelection = -1;
+    private String mSuggestionAction = null;
+    private Uri mSuggestionData = null;
+    private String mSuggestionQuery = null;
+
+    /**
+     * 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_Translucent);
+    }
+
+    /**
+     * 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.requestFeature(Window.FEATURE_NO_TITLE);
+        theWindow.setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+        theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
+
+        setContentView(com.android.internal.R.layout.search_bar);
+
+        // Note:  theWindow.setBackgroundDrawable(null) does not work here - you get blackness
+        theWindow.setBackgroundDrawableResource(android.R.color.transparent);
+
+        theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+        WindowManager.LayoutParams lp = theWindow.getAttributes();
+        lp.dimAmount = 0.5f;
+        lp.setTitle("Search Dialog");
+        theWindow.setAttributes(lp);
+
+        // get the view elements for local access
+        mSearchBarLayout = findViewById(com.android.internal.R.id.search_bar);
+        mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
+        mSearchEditLayout = (LinearLayout)findViewById(com.android.internal.R.id.search_edit_frame);
+        mSearchTextField = (EditText) findViewById(com.android.internal.R.id.search_src_text);
+        mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
+        mSuggestionsList = (ListView) findViewById(com.android.internal.R.id.search_suggest_list);
+        
+        // attach listeners
+        mSearchTextField.addTextChangedListener(mTextWatcher);
+        mSearchTextField.setOnKeyListener(mTextKeyListener);
+        mGoButton.setOnClickListener(mGoButtonClickListener);
+        mGoButton.setOnKeyListener(mGoButtonKeyListener);
+        mSuggestionsList.setOnItemClickListener(mSuggestionsListItemClickListener);
+        mSuggestionsList.setOnKeyListener(mSuggestionsKeyListener);
+        mSuggestionsList.setOnFocusChangeListener(mSuggestFocusListener);
+        mSuggestionsList.setOnItemSelectedListener(mSuggestSelectedListener);
+
+        // pre-hide all the extraneous elements
+        mBadgeLabel.setVisibility(View.GONE);
+        mSuggestionsList.setVisibility(View.GONE);
+
+        // Additional adjustments to make Dialog work for Search
+
+        // Touching outside of the search dialog will dismiss it 
+        setCanceledOnTouchOutside(true);
+        
+        // Preload animations
+        mSuggestionsEntry = AnimationUtils.loadAnimation(getContext(), 
+                com.android.internal.R.anim.grow_fade_in);
+        mSuggestionsExit = AnimationUtils.loadAnimation(getContext(), 
+                com.android.internal.R.anim.fade_out);
+
+        // 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");
+    }
+
+    /**
+     * 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
+        if (mSuggestionsList != null) {
+            mSuggestionsList.setVisibility(View.GONE);      // prevent any flicker if was visible
+        }
+        super.show();
+
+        setupSearchableInfo();
+        
+        // start the suggestions thread (which will mainly idle)
+        mSuggestionsRunner = new SuggestionsRunner();
+        new Thread(mSuggestionsRunner, "SearchSuggestions").start();
+
+        mLaunchComponent = componentName;
+        mAppSearchData = appSearchData;
+        mGlobalSearchMode = globalSearch;
+
+        // receive broadcasts
+        getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter);
+        getContext().registerReceiver(mBroadcastReceiver, mPackageFilter);
+        
+        mViewTreeObserver = mSearchBarLayout.getViewTreeObserver();
+        mViewTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+
+        // finally, load the user's initial text (which may trigger suggestions)
+        mNonUserQuery = false;
+        if (initialQuery == null) {
+            initialQuery = "";     // This forces the preload to happen, triggering suggestions
+        }
+        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;
+    }
+
+    /**
+     * Dismiss the search dialog.
+     * 
+     * This function is designed to be idempotent so it can be safely called at any time
+     * (even if already closed) and more likely to really dump any memory.  No leaks!
+     */
+    @Override
+    public void dismiss() {
+        if (isShowing()) {
+            super.dismiss();
+        }
+        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
+        }
+        
+        // ignore layout notifications
+        try {
+            if (mViewTreeObserver != null) {
+                mViewTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+            }
+        } catch (RuntimeException e) {
+            // This is OK - none registered or observer "dead"
+        } 
+        mViewTreeObserver = null;
+
+        // dump extra memory we're hanging on to
+        if (mSuggestionsRunner != null) {
+            mSuggestionsRunner.cancelSuggestions();
+            mSuggestionsRunner = null;
+        }
+        mLaunchComponent = null;
+        mAppSearchData = null;
+        mSearchable = null;
+        mSuggestionAction = null;
+        mSuggestionData = null;
+        mSuggestionQuery = null;
+        mActivityContext = null;
+        mProviderContext = 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 ((mSuggestionsList.getVisibility() == View.VISIBLE) && 
+                mSuggestionsList.isFocused()) {
+            selectedElement = mSuggestionsList.getSelectedItemPosition();   // 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;
+        }
+        mSkipNextAnimate = true;
+        
+        mNonUserQuery = true;
+        mSearchTextField.setText(displayQuery);
+        mNonUserQuery = 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;
+            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());
+            mProviderContext = mSearchable.getProviderContext(getContext(), mActivityContext);
+            
+            updateSearchButton();
+            updateSearchBadge();
+            updateQueryHint();
+        }
+    }
+
+    /**
+     * 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
+     */
+    private void updateSearchButton() {
+        int textId = mSearchable.getSearchButtonText();
+        if (textId == 0) {
+            textId = com.android.internal.R.string.search_go;
+        }
+        String goText = mActivityContext.getResources().getString(textId);
+        mGoButton.setText(goText);
+    }
+    
+    /**
+     * 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);
+        }
+    }
+
+    /**
+     * 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 (!mNonUserQuery) {
+                updateSuggestions();
+                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);
+    }
+
+    /**
+     * In response to a change in the query text, update the suggestions
+     */
+    private void updateSuggestions() {
+        final String queryText = mSearchTextField.getText().toString();
+        mPreviousSuggestionQuery = queryText;
+        if (DBG_LOG_TIMING == 1) {
+            dbgLogTiming("updateSuggestions()");
+        }
+        
+        mSuggestionsRunner.requestSuggestions(mSearchable, queryText);
+        
+        // For debugging purposes, put in a lot of strings (really fast typist)
+        if (DBG_JAM_THREADING > 0) {
+            for (int ii = 1; ii < DBG_JAM_THREADING; ++ii) {
+                final String jamQuery = queryText + ii;
+                mSuggestionsRunner.requestSuggestions(mSearchable, jamQuery);
+            }
+            // one final (correct) string for cleanup
+            mSuggestionsRunner.requestSuggestions(mSearchable, queryText);
+        }
+    }
+
+    /**
+     * This class defines a queued message structure for processing user keystrokes, and a
+     * thread that allows the suggestions to be gathered out-of-band, and allows us to skip
+     * over multiple keystrokes if the typist is faster than the content provider.
+     */
+    private class SuggestionsRunner implements Runnable {
+
+        private class Request {
+            final SearchableInfo mSearchableInfo;     // query will set these
+            final String mQueryText;
+            final boolean cancelRequest;              // cancellation will set this
+            
+            // simple constructors
+            Request(final SearchableInfo searchable, final String queryText) {
+                mSearchableInfo = searchable;
+                mQueryText = queryText;
+                cancelRequest = false;
+            }
+            
+            Request() {
+                mSearchableInfo = null;
+                mQueryText = null;
+                cancelRequest = true;
+            }
+        }
+        
+        private final LinkedBlockingQueue<Request> mSuggestionsQueue = 
+                                                            new LinkedBlockingQueue<Request>();
+
+        /**
+         * Queue up a suggestions request (non-blocking - can safely call from UI thread)
+         */
+        public void requestSuggestions(final SearchableInfo searchable, final String queryText) {
+            Request request = new Request(searchable, queryText);
+            try {
+                mSuggestionsQueue.put(request);
+            } catch (InterruptedException e) {
+                // discard the request.
+            }
+        }
+        
+        /**
+         * Cancel blocking suggestions, discard any results, and shut down the thread.
+         * (non-blocking - can safely call from UI thread)
+         */
+        private void cancelSuggestions() {
+            Request request = new Request();
+            try {
+                mSuggestionsQueue.put(request);
+            } catch (InterruptedException e) {
+                // discard the request.
+                // TODO can we do better here?
+            }
+        }
+        
+        /**
+         * This runnable implements the logic for decoupling keystrokes from suggestions.  
+         * The logic isn't quite obvious here, so I'll try to describe it.
+         * 
+         * Normally we simply sleep waiting for a keystroke.  When a keystroke arrives,
+         * we immediately dispatch a request to gather suggestions.  
+         * 
+         * But this can take a while, so by the time it comes back, more keystrokes may have 
+         * arrived.  If anything happened while we were gathering the suggestion, we discard its 
+         * results, and then use the most recent keystroke to start the next suggestions request.
+         * 
+         * Any request containing cancelRequest == true will cause the thread to immediately
+         * terminate.
+         */
+        public void run() {            
+            // outer blocking loop simply waits for a suggestion
+            while (true) {
+                try {
+                    Request request = mSuggestionsQueue.take();
+                    if (request.cancelRequest) {
+                        return;
+                    }
+                    
+                    // since we were idle, what we're really interested is the final element
+                    // in the queue.  So keep pulling until we get the last element.
+                    // TODO Could we just do some sort of takeHead() here?
+                    while (! mSuggestionsQueue.isEmpty()) {
+                        request = mSuggestionsQueue.take();
+                        if (request.cancelRequest) {
+                            return;
+                        }
+                    }
+                    final Request useRequest = request;
+                    
+                    // now process the final element (unless it's a cancel - that can be discarded)
+                    
+                    if (useRequest.mSearchableInfo != null) {
+                        
+                        // go get the cursor.  this is what takes time.
+                        final Cursor c = getSuggestions(useRequest.mSearchableInfo, 
+                                useRequest.mQueryText);
+                        
+                        // We now have a suggestions result.  But, if any new requests have arrived,
+                        // we're going to discard them - we don't want to waste time displaying 
+                        // out-of-date results, we just want to get going on the next set.
+                        // Note, null cursor is a valid result (no suggestions).  This logic also
+                        // supports the need to discard the results *and* stop the thread if a kill 
+                        // request arrives during a query.
+                        if (mSuggestionsQueue.size() > 0) {
+                            if (c != null) {
+                                c.close();
+                            }
+                        } else {
+                            mHandler.post(new Runnable() {
+                                public void run() {
+                                    updateSuggestionsWithCursor(c, useRequest.mSearchableInfo);
+                                } 
+                            });
+                        }
+                    }
+                } catch (InterruptedException e) {
+                    // loop back for more
+                }
+                // At this point the queue may contain zero-to-many new requests;  We simply 
+                // loop back to handle them (or, block until new requests arrive)
+            }
+        }
+    }
+        
+    /**
+     * Back in the UI thread, handle incoming cursors
+     */
+    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};
+    
+    /**
+     * A new cursor (with suggestions) is ready for use.  Update the UI.
+     */
+    void updateSuggestionsWithCursor(Cursor c, final SearchableInfo searchable) {
+        ListAdapter adapter = null;
+        
+        // first, check for various conditions that disqualify this cursor
+        if ((c == null) || (c.getCount() == 0)) {
+            // no cursor, or cursor with no data
+        } else if ((searchable != mSearchable) || !isShowing()) {
+            // race condition (suggestions arrived after conditions changed)
+        } else {
+            // 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;
+                    }
+                }
+                try {
+                    if (DBG_LOG_TIMING == 1) {
+                        dbgLogTiming("updateSuggestions(3)");
+                    }
+                    adapter = new SuggestionsCursorAdapter(getContext(), layout, c, from, to, 
+                            mProviderContext);
+                    if (DBG_LOG_TIMING == 1) {
+                        dbgLogTiming("updateSuggestions(4)");
+                    }
+                } catch (RuntimeException e) {
+                    Log.e(LOG_TAG, "Exception while creating SuggestionsCursorAdapter", e);
+                }
+            }
+            
+            // Provide some help for developers instead of just silently discarding
+            if ((colIc1 >= 0) != (colIc2 >= 0)) {
+                Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns.");
+            } else if (adapter == null) {
+                Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns.");
+            }
+        }
+        
+        // if we have a cursor but we're not using it (e.g. disqualified), close it now
+        if ((c != null) && (adapter == null)) {
+            c.close();
+            c = null;
+        }
+        
+        // we only made an adapter if there were 1+ suggestions.  Now, based on the existence
+        // of the adapter, we'll also show/hide the list.
+        discardListCursor(mSuggestionsList);
+        if (adapter == null) {
+            showSuggestions(false, !mSkipNextAnimate);
+        } else {
+            layoutSuggestionsList();
+            showSuggestions(true, !mSkipNextAnimate);
+        }
+        mSkipNextAnimate = false;
+        if (DBG_LOG_TIMING == 1) {
+            dbgLogTiming("updateSuggestions(5)");
+        }
+        mSuggestionsList.setAdapter(adapter);
+        // now that we have an adapter, we can actually adjust the selection & scroll positions
+        if (mPresetSelection >= 0) {
+            boolean bTouchMode = mSuggestionsList.isInTouchMode();
+            mSuggestionsList.setSelection(mPresetSelection);
+            mPresetSelection = -1;
+        }
+        if (DBG_LOG_TIMING == 1) {
+            dbgLogTiming("updateSuggestions(6)");
+        }
+    }
+    
+    /**
+     * Utility for showing & hiding the suggestions list.  This is also responsible for triggering
+     * animation, if any, at the right time.
+     * 
+     * @param visible If true, show the suggestions, if false, hide them.
+     * @param animate If true, use animation.  If false, "just do it."
+     */
+    private void showSuggestions(boolean visible, boolean animate) {
+        if (visible) {
+            if (animate && (mSuggestionsList.getVisibility() != View.VISIBLE)) {
+                mSuggestionsList.startAnimation(mSuggestionsEntry);
+            }
+            mSuggestionsList.setVisibility(View.VISIBLE);
+        } else {
+            if (animate && (mSuggestionsList.getVisibility() != View.GONE)) {
+                mSuggestionsList.startAnimation(mSuggestionsExit);
+            }
+            mSuggestionsList.setVisibility(View.GONE);
+        }
+    }
+    
+    /**
+     * This helper class supports the suggestions list by allowing 3rd party (e.g. app) resources
+     * to be used in suggestions
+     */
+    private static class SuggestionsCursorAdapter extends SimpleCursorAdapter {
+        
+        private Resources mProviderResources;
+        
+        public SuggestionsCursorAdapter(Context context, int layout, Cursor c,
+                String[] from, int[] to, Context providerContext) {
+            super(context, layout, c, from, to);
+            mProviderResources = providerContext.getResources();
+        }
+        
+        /**
+         * 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).
+         *
+         * @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.
+         */
+        @Override 
+        /**
+         * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
+         */
+        public View getView(int position, View convertView, ViewGroup parent) {
+            try {
+                return super.getView(position, convertView, parent);
+            } catch (RuntimeException e) {
+                Log.w(LOG_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;
+            }
+        }
+    }
+    
+    /**
+     * Cleanly close the cursor being used by a ListView.  Do this before replacing the adapter
+     * or before closing the ListView.
+     */
+    private void discardListCursor(ListView list) {
+        CursorAdapter ca = getSuggestionsAdapter(list);
+        if (ca != null) {
+            Cursor c = ca.getCursor();
+            if (c != null) {
+                ca.changeCursor(null);
+            }
+        }
+    }
+    
+    /**
+     * 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;
+    }
+
+    /**
+     * Get the query cursor for the search suggestions.
+     * 
+     * @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) {    // if selection provided, use it
+                    selArgs = new String[] {query};
+                } else {
+                    uriStr.append('/');                             // no sel, use REST pattern
+                    uriStr.append(Uri.encode(query));
+                }
+
+                // finally, make the query
+                if (DBG_LOG_TIMING == 1) {
+                    dbgLogTiming("getSuggestions(1)");
+                }
+                cursor = getContext().getContentResolver().query(
+                                                        Uri.parse(uriStr.toString()), null, 
+                                                        searchable.getSuggestSelection(), selArgs,
+                                                        null);
+                if (DBG_LOG_TIMING == 1) {
+                    dbgLogTiming("getSuggestions(2)");
+                }
+            } catch (RuntimeException e) {
+                Log.w(LOG_TAG, "Search Suggestions query returned exception " + e.toString());
+                cursor = null;
+            }
+        }
+        
+        return cursor;
+    }
+
+    /**
+     * React to typing in the GO button by refocusing to EditText.  Continue typing the query.
+     */
+    View.OnKeyListener mGoButtonKeyListener = 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 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) {
+            // 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()");
+                }
+                switch (keyCode) {
+                case KeyEvent.KEYCODE_ENTER:
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                    if (event.getAction() == KeyEvent.ACTION_UP) {
+                        v.cancelLongPress();
+                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);                    
+                        return true;
+                    }
+                    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).
+     */
+    View.OnKeyListener mSuggestionsKeyListener = new View.OnKeyListener() {
+        public boolean onKey(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);
+                if (!handled) {
+                    handled = refocusingKeyListener(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.
+     */
+    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);
+                    }
+                }
+            }
+
+        }
+    };
+    
+    /**
+     * Update query text based on movement of selection in/out of suggestion list
+     */
+    OnItemSelectedListener mSuggestSelectedListener = new OnItemSelectedListener() {
+        public void onItemSelected(AdapterView parent, View view, int position, long id) {
+            // Update query text while user navigates through suggestions list
+            // also guard against possible race conditions (late arrival after dismiss)
+            if (mSearchable != null && position >= 0 && mSuggestionsList.isFocused()) {
+                jamSuggestionQuery(true, parent, position);
+            }
+        }
+
+        // No action needed on this callback
+        public void onNothingSelected(AdapterView parent) { }        
+    };
+
+    /**
+     * 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();
+            }
+        }
+    };
+
+    /**
+     * UI-thread handling of dialog dismiss.  Called by mBroadcastReceiver.onReceive().
+     *
+     * TODO: This is a really heavyweight solution for something that should be so simple.
+     * For example, we already have a handler, in our superclass, why aren't we sharing that?
+     * I think we need to investigate simplifying this entire methodology, or perhaps boosting
+     * it up into the Dialog class.
+     */
+    private static final int MESSAGE_DISMISS = 0;
+    private Handler mDismissHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MESSAGE_DISMISS) {
+                dismiss();
+            }
+        }
+    };
+    
+    /**
+     * Listener for layout changes in the main layout.  I use this to dynamically clean up 
+     * the layout of the dropdown and make it "pixel perfect."
+     */
+    private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener 
+        = new ViewTreeObserver.OnGlobalLayoutListener() {
+        
+        // It's very important that layoutSuggestionsList() does not reset
+        // the values more than once, or this becomes an infinite loop.
+        public void onGlobalLayout() {
+            layoutSuggestionsList();
+        }
+    };
+
+    /**
+     * 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) {
+        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) {
+                AdapterView<?> av = (AdapterView<?>) v;
+                int position = av.getSelectedItemPosition();
+                return launchSuggestion(av, position);
+            }
+            
+            // Next, check for left/right moves while we'll manually grab and shift focus
+            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
+                mLeaveJammedQueryOnRefocus = true;
+                if (mSearchTextField.requestFocus()) {
+                    mLeaveJammedQueryOnRefocus = false;
+                    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
+                        mSearchTextField.setSelection(0);
+                    } else {
+                        mSearchTextField.setSelection(mSearchTextField.length());
+                    }
+                    return true;
+                }
+                mLeaveJammedQueryOnRefocus = 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
+                ListView lv = (ListView) v;
+                int position = lv.getSelectedItemPosition();
+                if (position >= 0) {
+                    CursorAdapter ca = getSuggestionsAdapter(lv);
+                    Cursor c = ca.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) {
+        mNonUserQuery = 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();
+                }
+            }
+        } 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();
+            }
+        }
+        mNonUserQuery = 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);
+    }
+
+    /**
+     * Handler for clicks in the suggestions list
+     */
+    private OnItemClickListener mSuggestionsListItemClickListener = new OnItemClickListener() {
+        public void onItemClick(AdapterView parent, View v, int position, long id) {
+            // this guard protects against possible race conditions (late arrival of click)
+            if (mSearchable != null) {
+                launchSuggestion(parent, position);
+            }
+        }
+    };
+    
+    /**
+     * Shared code for launching a query from a suggestion.
+     * 
+     * @param av The AdapterView (really a ListView) containing the suggestions
+     * @param position The suggestion we'll be launching from
+     * 
+     * @return Returns true if a successful launch, false if could not (e.g. bad position)
+     */
+    private boolean launchSuggestion(AdapterView<?> av, int position) {
+        CursorAdapter ca = getSuggestionsAdapter(av);
+        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;
+    }
+    
+    /**
+     * Manually adjust suggestions list into its perfectly-tweaked position.
+     * 
+     * NOTE:  This MUST not adjust the parameters if they are already set correctly,
+     * or you create an infinite loop via the ViewTreeObserver.OnGlobalLayoutListener callback. 
+     */
+    private void layoutSuggestionsList() {
+        final int FUDGE_SUGG_X = 1;
+        final int FUDGE_SUGG_WIDTH = 2;
+        
+        int[] itemLoc = new int[2];
+        mSearchTextField.getLocationOnScreen(itemLoc);
+        int x,width;
+        x = itemLoc[0] + FUDGE_SUGG_X;
+        width = mSearchTextField.getMeasuredWidth() + FUDGE_SUGG_WIDTH;
+        
+        // now set params and relayout
+        ViewGroup.MarginLayoutParams lp;
+        lp = (ViewGroup.MarginLayoutParams) mSuggestionsList.getLayoutParams();
+        boolean changing = (lp.width != width) || (lp.leftMargin != x);
+        if (changing) {
+            lp.leftMargin = x;
+            lp.width = width;
+            mSuggestionsList.setLayoutParams(lp);
+        }
+    }
+    
+    /**
+     * 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;
+    }
+
+    /**
+     * 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..01babc4
--- /dev/null
+++ b/core/java/android/app/SearchManager.java
@@ -0,0 +1,1328 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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>
+ * </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="android.app.Activity#ActivityLifecycle">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="../../../devel/bblocks-manifest.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="../../../devel/bblocks-manifest.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="../../../devel/bblocks-manifest.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 intent 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="../../../devel/bblocks-manifest.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="../../../devel/bblocks-manifest.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>
+ *     
+ *     </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="../../../devel/resources-i18n.html#AlternateResources">Resources and 
+ * Internationalization: Supporting Alternate Resources for Alternate Languages and Configurations
+ * </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="../../../devel/bblocks-manifest.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="../../../devel/bblocks-manifest.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="../../../devel/bblocks-manifest.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>
+ */
+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 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..28b0615
--- /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 the
+ * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
+ * Application Model overview</a>.</p>
+ * 
+ * <p>The Service class is an important part of an
+ * <a href="{@docRoot}intro/lifecycle.html">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}reference/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}devel/security.html">Security Model</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.
+     * If you override this method, be sure to call super.onCreate().
+     */
+    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.
+     * If you override this method, be sure to call super.onStart().
+     *  
+     * @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.
+     * If you override this method, be sure to call super.onDestroy().
+     */
+    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}reference/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 the <a href="{@docRoot}intro/appmodel.html#Threads">Threading section
+     * of the Application Model overview</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.
+     *
+     * @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..107532e
--- /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.pim.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/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/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
new file mode 100644
index 0000000..0b24db6
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -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.
+ */
+
+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 {
+    public static final int MODE_UNKNOWN = -1;
+    public static final int MODE_OFF = 0;
+    public static final int MODE_CONNECTABLE = 1;
+    public static final int MODE_DISCOVERABLE = 2;
+
+    public static final int RESULT_FAILURE = -1;
+    public static final int RESULT_SUCCESS = 0;
+
+    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 getMajorClass() {
+        try {
+            return mService.getMajorClass();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getMinorClass() {
+        try {
+            return mService.getMinorClass();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    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;
+    }
+
+    public int getMode() {
+        try {
+            return mService.getMode();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return MODE_UNKNOWN;
+    }
+    public void setMode(int mode) {
+        try {
+            mService.setMode(mode);
+        } 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. BluetoothIntent.BONDING_CREATED_ACTION
+     * will be broadcast if and when the remote device is successfully bonded.
+     *
+     * @param address the remote device Bluetooth address.
+     * @return false if we cannot create a bonding to that device, true if
+     * there were no problems beginning the bonding process.
+     */
+    public boolean createBonding(String address) {
+        return createBonding(address, null);
+    }
+
+    /**
+     * Create a bonding with a remote bluetooth device.
+     *
+     * This is an asynchronous call. onCreateBondingResult() of your callback
+     * will be called when the call is complete, with either RESULT_SUCCESS or
+     * RESULT_FAILURE.
+     *
+     * In addition to the callback, BluetoothIntent.BONDING_CREATED_ACTION will
+     * be broadcast if the remote device is successfully bonded.
+     *
+     * @param address The remote device Bluetooth address.
+     * @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 createBonding(String address, IBluetoothDeviceCallback callback) {
+        try {
+            return mService.createBonding(address, callback);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public boolean cancelBondingProcess(String address) {
+        try {
+            return mService.cancelBondingProcess(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.
+     *
+     * @return bluetooth hardware addresses of remote devices that are
+     *         bonded. Array size is 0 if no devices are bonded. Null on error.
+     */
+    public String[] listBondings() {
+        try {
+            return mService.listBondings();
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    /**
+     * Check if a remote device is 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 checks if we have a link key with the remote device. 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
+     * a 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.
+     *
+     * @param address Bluetooth hardware address of the remote device to check.
+     * @return true if bonded, false otherwise and on error.
+     */
+    public boolean hasBonding(String address) {
+        try {
+            return mService.hasBonding(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public boolean removeBonding(String address) {
+        try {
+            return mService.removeBonding(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+
+    public String getRemoteName(String address) {
+        try {
+            return mService.getRemoteName(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+
+    public String getRemoteAlias(String address) {
+        try {
+            return mService.getRemoteAlias(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public boolean setRemoteAlias(String address, String alias) {
+        try {
+            return mService.setRemoteAlias(address, alias);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+    public boolean clearRemoteAlias(String address) {
+        try {
+            return mService.clearRemoteAlias(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return false;
+    }
+    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;
+    }
+    public String getRemoteMajorClass(String address) {
+        try {
+            return mService.getRemoteMajorClass(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String getRemoteMinorClass(String address) {
+        try {
+            return mService.getRemoteMinorClass(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return null;
+    }
+    public String[] getRemoteServiceClasses(String address) {
+        try {
+            return mService.getRemoteServiceClasses(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;
+    }
+
+    public int getRemoteClass(String address) {
+        try {
+            return mService.getRemoteClass(address);
+        } catch (RemoteException e) {Log.e(TAG, "", e);}
+        return DeviceClass.CLASS_UNKNOWN;
+    }
+    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;
+    }
+    
+
+    /* Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */
+    private static final int ADDRESS_LENGTH = 17;
+    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/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
new file mode 100644
index 0000000..90db39b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+ *
+ * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
+ * Service.
+ *
+ * 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.
+ *
+ * BlueoothHeadset objects are not guarenteed to be connected to the
+ * BluetoothHeadsetService at all times. Calls on this object while not
+ * connected to the service will result in default error return values. Even
+ * after object construction, there is a short delay (~10ms) before this proxy
+ * object is actually connected to the Service.
+ *
+ * Android only supports one connected Bluetooth Headset at a time.
+ *
+ * Note that in this context, Headset includes both Bluetooth Headset's and
+ * Handsfree devices.
+ *
+ * @hide
+ */
+public class BluetoothHeadset {
+
+    private final static String TAG = "BluetoothHeadset";
+
+    private final Context mContext;
+    private IBluetoothHeadset mService;
+
+    /** 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 cancelled before completetion. */
+    public static final int RESULT_CANCELLED = 2;
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mService = IBluetoothHeadset.Stub.asInterface(service);
+            Log.i(TAG, "Proxy object is now connected to Bluetooth Headset Service");
+        }
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+        }
+    };
+
+    /**
+     * Create a BluetoothHeadset proxy object.
+     * Remeber to call close() when you are done with this object, so that it
+     * can unbind from the BluetoothHeadsetService.
+     */
+    public BluetoothHeadset(Context context) {
+        mContext = context;
+        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());}
+        }
+        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());}
+        }
+        return null;
+    }
+
+    /**
+     * Request to initiate a connection to a headset.
+     * This call does not block. Fails if a headset is already connecting
+     * or connected.
+     * Will connect to the last connected headset if address is null.
+     * @param address The Bluetooth Address to connect to, or null to connect
+     *                to the last connected headset.
+     * @param callback A callback with onCreateBondingResult() defined, or
+     *                 null.
+     * @return        False if there was a problem initiating the connection
+     *                procedure, and your callback will not be used. True if
+     *                the connection procedure was initiated, in which case
+     *                your callback is guarenteed to be called.
+     */
+    public boolean connectHeadset(String address, IBluetoothHeadsetCallback callback) {
+        if (mService != null) {
+            try {
+                return mService.connectHeadset(address, callback);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        }
+        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());}
+        }
+        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();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        }
+        return false;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java
new file mode 100644
index 0000000..8e22791
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothIntent.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 MODE =
+        "android.bluetooth.intent.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";
+
+    @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";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String MODE_CHANGED_ACTION         =
+        "android.bluetooth.intent.action.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";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_ALIAS_CHANGED_ACTION       =
+        "android.bluetooth.intent.action.REMOTE_ALIAS_CHANGED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String REMOTE_ALIAS_CLEARED_ACTION       =
+        "android.bluetooth.intent.action.REMOTE_ALIAS_CLEARED";
+
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String BONDING_CREATED_ACTION            =
+        "android.bluetooth.intent.action.BONDING_CREATED";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String BONDING_REMOVED_ACTION            =
+        "android.bluetooth.intent.action.BONDING_REMOVED";
+
+    @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/DeviceClass.java b/core/java/android/bluetooth/DeviceClass.java
new file mode 100644
index 0000000..36035ca
--- /dev/null
+++ b/core/java/android/bluetooth/DeviceClass.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 device
+ * class. This is actually a bit vector, the format 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), Major Class, and Minor Class are encoded
+ * in a 32-bit device class.
+ *
+ * Each of the helper methods takes the 32-bit integer device class as an
+ * argument.
+ *
+ * @hide
+ */
+public class DeviceClass {
+
+    // Baseband class information
+    // See http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+
+    public static final int SERVICE_CLASS_BITMASK                 = 0xFFE000;
+    public static final int SERVICE_CLASS_LIMITED_DISCOVERABILITY = 0x002000;
+    public static final int SERVICE_CLASS_POSITIONING             = 0x010000;
+    public static final int SERVICE_CLASS_NETWORKING              = 0x020000;
+    public static final int SERVICE_CLASS_RENDER                  = 0x040000;
+    public static final int SERVICE_CLASS_CAPTURE                 = 0x080000;
+    public static final int SERVICE_CLASS_OBJECT_TRANSFER         = 0x100000;
+    public static final int SERVICE_CLASS_AUDIO                   = 0x200000;
+    public static final int SERVICE_CLASS_TELEPHONY               = 0x400000;
+    public static final int SERVICE_CLASS_INFORMATION             = 0x800000;
+
+    public static final int MAJOR_CLASS_BITMASK           = 0x001F00;
+    public static final int MAJOR_CLASS_MISC              = 0x000000;
+    public static final int MAJOR_CLASS_COMPUTER          = 0x000100;
+    public static final int MAJOR_CLASS_PHONE             = 0x000200;
+    public static final int MAJOR_CLASS_NETWORKING        = 0x000300;
+    public static final int MAJOR_CLASS_AUDIO_VIDEO       = 0x000400;
+    public static final int MAJOR_CLASS_PERIPHERAL        = 0x000500;
+    public static final int MAJOR_CLASS_IMAGING           = 0x000600;
+    public static final int MAJOR_CLASS_WEARABLE          = 0x000700;
+    public static final int MAJOR_CLASS_TOY               = 0x000800;
+    public static final int MAJOR_CLASS_MEDICAL           = 0x000900;
+    public static final int MAJOR_CLASS_UNCATEGORIZED     = 0x001F00;
+
+    // Minor classes for the AUDIO_VIDEO major class
+    public static final int MINOR_CLASS_AUDIO_VIDEO_BITMASK                       = 0x0000FC;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_UNCATEGORIZED                 = 0x000000;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_HEADSET                       = 0x000004;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_HANDSFREE                     = 0x000008;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_MICROPHONE                    = 0x000010;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_LOUDSPEAKER                   = 0x000014;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_HEADPHONES                    = 0x000018;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_PORTABLE_AUDIO                = 0x00001C;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_CAR_AUDIO                     = 0x000020;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_SET_TOP_BOX                   = 0x000024;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_HIFI_AUDIO                    = 0x000028;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_VCR                           = 0x00002C;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_CAMERA                  = 0x000030;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_CAMCORDER                     = 0x000034;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_MONITOR                 = 0x000038;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x00003C;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_CONFERENCING            = 0x000040;
+    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_GAMING_TOY              = 0x000048;
+
+    // Indicates the Bluetooth API could not retrieve the class
+    public static final int CLASS_UNKNOWN = 0xFF000000;
+
+    /** Returns true if the given device class supports the given Service Class.
+     * A bluetooth device can claim to support zero or more service classes.
+     * @param deviceClass      The bluetooth device class.
+     * @param serviceClassType The service class constant to test for. For
+     *                         example, DeviceClass.SERVICE_CLASS_AUDIO. This
+     *                         must be one of the SERVICE_CLASS_xxx constants,
+     *                         results of this function are undefined
+     *                         otherwise.
+     * @return If the deviceClass claims to support the serviceClassType.
+     */
+    public static boolean hasServiceClass(int deviceClass, int serviceClassType) {
+        if (deviceClass == CLASS_UNKNOWN) {
+            return false;
+        }
+        return ((deviceClass & SERVICE_CLASS_BITMASK & serviceClassType) != 0);
+    }
+
+    /** Returns the Major Class of a bluetooth device class.
+     * Values returned from this function can be compared with the constants
+     * MAJOR_CLASS_xxx. A bluetooth device can only be associated
+     * with one major class.
+     */
+    public static int getMajorClass(int deviceClass) {
+        if (deviceClass == CLASS_UNKNOWN) {
+            return CLASS_UNKNOWN;
+        }
+        return (deviceClass & MAJOR_CLASS_BITMASK);
+    }
+
+    /** Returns the Minor Class of a bluetooth device class.
+     * Values returned from this function can be compared with the constants
+     * MINOR_CLASS_xxx_yyy, where xxx is the Major Class. A bluetooth
+     * device can only be associated with one minor class within its major
+     * class.
+     */
+    public static int getMinorClass(int deviceClass) {
+        if (deviceClass == CLASS_UNKNOWN) {
+            return CLASS_UNKNOWN;
+        }
+        return (deviceClass & MINOR_CLASS_AUDIO_VIDEO_BITMASK);
+    }
+}
diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java
new file mode 100644
index 0000000..bce3388
--- /dev/null
+++ b/core/java/android/bluetooth/HeadsetBase.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+import java.io.IOException;
+import java.lang.Thread;
+
+/**
+ * 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();
+
+        // Microphone Gain
+        mAtParser.register("+VGM", new AtCommandHandler() {
+            @Override
+            public AtCommandResult handleSetCommand(Object[] args) {
+                // AT+VGM=<gain>    in range [0,15]
+                // Headset/Handsfree is reporting its current gain setting
+                //TODO: sync to android UI
+                //TODO: Send unsolicited +VGM when volume changed on AG
+                return new AtCommandResult(AtCommandResult.OK);
+            }
+        });
+
+        // Speaker Gain
+        mAtParser.register("+VGS", new AtCommandHandler() {
+            @Override
+            public AtCommandResult handleSetCommand(Object[] args) {
+                // AT+VGS=<gain>    in range [0,15]
+                // Headset/Handsfree is reporting its current gain to Android
+                //TODO: sync to AG UI
+                //TODO: Send unsolicited +VGS when volume changed on AG
+                return new AtCommandResult(AtCommandResult.OK);
+            }
+        });
+    }
+
+    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/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl
new file mode 100644
index 0000000..e7cc8ed
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothDevice.aidl
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 getMajorClass();
+    String getMinorClass();
+    String getVersion();
+    String getRevision();
+    String getManufacturer();
+    String getCompany();
+
+    int getMode();
+    boolean setMode(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 createBonding(in String address, in IBluetoothDeviceCallback callback);
+    boolean cancelBondingProcess(in String address);
+    String[] listBondings();
+    boolean hasBonding(in String address);
+    boolean removeBonding(in String address);
+
+    String getRemoteName(in String address);
+    String getRemoteAlias(in String address);
+    boolean setRemoteAlias(in String address, in String alias);
+    boolean clearRemoteAlias(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);
+    String getRemoteMajorClass(in String address);
+    String getRemoteMinorClass(in String address);
+    String[] getRemoteServiceClasses(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..86f44dd
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothDeviceCallback.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 android.bluetooth;
+
+/**
+ * {@hide}
+ */
+oneway interface IBluetoothDeviceCallback
+{
+    void onCreateBondingResult(in String address, int result);
+    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..7b6030b
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHeadset.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.bluetooth;
+
+import android.bluetooth.IBluetoothHeadsetCallback;
+
+/**
+ * System private API for Bluetooth Headset service
+ *
+ * {@hide}
+ */
+interface IBluetoothHeadset {
+    int getState();
+
+    String getHeadsetAddress();
+
+    // Request that the given headset be connected
+    // Assumes the given headset is already bonded
+    // Will disconnect any currently connected headset
+    // returns false if cannot start a connection (for example, there is
+    // already a pending connect). callback will always be called iff this
+    // returns true
+    boolean connectHeadset(in String address, in IBluetoothHeadsetCallback callback);
+
+    boolean isConnected(in String address);
+
+    void disconnectHeadset();
+}
diff --git a/core/java/android/bluetooth/IBluetoothHeadsetCallback.aidl b/core/java/android/bluetooth/IBluetoothHeadsetCallback.aidl
new file mode 100644
index 0000000..03e884b
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHeadsetCallback.aidl
@@ -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.bluetooth;
+
+/**
+ * {@hide}
+ */
+oneway interface IBluetoothHeadsetCallback
+{
+    void onConnectHeadsetResult(in String address, int resultCode);
+}
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..75b33296
--- /dev/null
+++ b/core/java/android/bluetooth/ScoSocket.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.bluetooth;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.Thread;
+
+
+/**
+ * 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 STATE_CONNECTING or STATE_CONNECTED
+
+    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);
+        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();
+        if (result < 0) {
+            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) {
+            acquireWakeLock();
+            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..ccd8fec
--- /dev/null
+++ b/core/java/android/bluetooth/package.html
@@ -0,0 +1,14 @@
+<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>
+{@hide}
+</BODY>
+</HTML>
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
new file mode 100644
index 0000000..56e5d4a
--- /dev/null
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static android.provider.SyncConstValue._SYNC_ACCOUNT;
+import static android.provider.SyncConstValue._SYNC_DIRTY;
+import static android.provider.SyncConstValue._SYNC_ID;
+import static android.provider.SyncConstValue._SYNC_LOCAL_ID;
+import static android.provider.SyncConstValue._SYNC_MARK;
+import static android.provider.SyncConstValue._SYNC_VERSION;
+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, "_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_ID_AND_ACCOUNT =
+            _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
+
+    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
+     * contains the complete projection of the table.
+     * <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");
+    }
+
+    private 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 localPersonID = 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;
+                    localPersonID = 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 set and > -1 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
+            long serverLocalSyncId =
+                    diffsCursor.isNull(serverSyncLocalIdColumn)
+                            ? -1
+                            : diffsCursor.getLong(serverSyncLocalIdColumn);
+            if (serverLocalSyncId > -1) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "the remote record with sync id "
+                        + serverSyncId + " has a local sync id, "
+                        + serverLocalSyncId);
+                localSyncID = serverSyncId;
+                localSyncDirty = false;
+                localPersonID = serverLocalSyncId;
+                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 " +
+                                    localPersonID);
+                        }
+                        conflict = true;
+                    } else {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                             Log.v(TAG,
+                                     "remote record " +
+                                             serverSyncId +
+                                     " updates local _sync_id " +
+                                     localSyncID + ", local _id " +
+                                     localPersonID);
+                         }
+                         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(localPersonID, serverDiffs, diffsCursor);
+                syncResult.stats.numUpdates++;
+            } else if (conflict) {
+                resolveRow(localPersonID, 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);
+            serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
+
+            while (diffsCursor.moveToNext()) {
+                if (mIsMergeCancelled) {
+                    diffsCursor.deactivate();
+                    return;
+                }
+                // delete all rows that match each element in the diffsCursor
+                fullyDeleteRowsWithSyncId(diffsCursor.getString(serverSyncIDColumn), account,
+                        syncResult);
+                mDb.yieldIfContended();
+            }
+            diffsCursor.deactivate();
+        }
+    }
+
+    private void fullyDeleteRowsWithSyncId(String syncId, String account, SyncResult syncResult) {
+        final String[] selectionArgs = new String[]{syncId, account};
+        // delete the rows explicitly so that the delete operation can be overridden
+        Cursor c = mDb.query(mTable, new String[]{"_id"}, SELECT_BY_ID_AND_ACCOUNT,
+                selectionArgs, null, null, null);
+        try {
+            c.moveToFirst();
+            while (!c.isAfterLast()) {
+                deleteRow(c); // advances the cursor
+                syncResult.stats.numDeletes++;
+            }
+        } finally {
+            c.deactivate();
+        }
+        if (mDeletedTable != null) {
+            mDb.delete(mDeletedTable, SELECT_BY_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 + "=?", 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..48f1bc7
--- /dev/null
+++ b/core/java/android/content/AsyncQueryHandler.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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 */ 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) {
+            WorkerArgs args = (WorkerArgs) msg.obj;
+
+            int token = msg.what;
+            int event = msg.arg1;
+            
+            switch (event) {
+                case EVENT_ARG_QUERY:
+                    Cursor cursor;
+                    try {
+                        cursor = mResolver.query(args.uri, args.projection,
+                                args.selection, args.selectionArgs,
+                                args.orderBy);
+                    } catch (Exception e) {
+                        cursor = null;
+                    }
+
+                    args.result = cursor;
+                    break;
+
+                case EVENT_ARG_INSERT:
+                    args.result = mResolver.insert(args.uri, args.values);
+                    break;
+
+                case EVENT_ARG_UPDATE:
+                    int r = mResolver.update(args.uri, args.values, args.selection,
+                            args.selectionArgs);
+                    args.result = new Integer(r);
+                    break;
+
+                case EVENT_ARG_DELETE:
+                    int r2 = mResolver.delete(args.uri, args.selection, args.selectionArgs);
+                    args.result = new Integer(r2);
+                    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 = 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}
+     */
+    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..6a6f4f9
--- /dev/null
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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}intro/lifecycle.html">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}devel/security.html">Security Model</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)}.
+     * 
+     * @param context The Context in which the receiver is running.
+     * @param intent The Intent being received.
+     */
+    public abstract void onReceive(Context context, Intent intent);
+
+    /**
+     * 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 intent 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..226c5ab
--- /dev/null
+++ b/core/java/android/content/ContentProvider.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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>See <a href="{@docRoot}devel/data/contentproviders.html">this page</a> for more information on
+ * content providers.</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 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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
+     * the Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
+     * the Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
+     * the Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
+     * the Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
+     * Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
+     * the Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
+     * Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
+     * Application Model overview</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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
+     * Application Model overview</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
+     * or "rw" for read and write access.
+     *
+     * @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.
+     */
+    public ParcelFileDescriptor openFile(Uri uri, String mode)
+            throws FileNotFoundException {
+        throw new FileNotFoundException("No files supported by provider at "
+                + uri);
+    }
+
+    /**
+     * 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.
+     *
+     * @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;
+        if ("r".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
+        } else if ("rw".equals(mode)) {
+            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
+                    | ParcelFileDescriptor.MODE_CREATE;
+        } else {
+            throw new FileNotFoundException("Bad mode for " + 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 the
+     * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
+     * the Application Model overview</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..ede2c9b
--- /dev/null
+++ b/core/java/android/content/ContentProviderNative.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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 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..52f55b6
--- /dev/null
+++ b/core/java/android/content/ContentResolver.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.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>
+     * <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 "content:" URI.
+     * @return InputStream
+     * @throws FileNotFoundException if the provided URI could not be opened.
+     */
+    public final InputStream openInputStream(Uri uri)
+            throws FileNotFoundException {
+        String scheme = uri.getScheme();
+        if (SCHEME_CONTENT.equals(scheme)) {
+            ParcelFileDescriptor fd = openFileDescriptor(uri, "r");
+            return fd != null ? new ParcelFileDescriptor.AutoCloseInputStream(fd) : null;
+        } else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+            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);
+            }
+            try {
+                InputStream stream = r.openRawResource(id);
+                return stream;
+            } catch (Resources.NotFoundException ex) {
+                throw new FileNotFoundException("Resource ID does not exist: " + uri);
+            }
+        } else if (SCHEME_FILE.equals(scheme)) {
+            return new FileInputStream(uri.getPath());
+        } else {
+            throw new FileNotFoundException("Unknown scheme: " + uri);
+        }
+    }
+
+    /**
+     * 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>
+     * </ul>
+     *
+     * @param uri The desired "content:" URI.
+     * @return OutputStream
+     */
+    public final OutputStream openOutputStream(Uri uri)
+            throws FileNotFoundException {
+        String scheme = uri.getScheme();
+        if (SCHEME_CONTENT.equals(scheme)) {
+            ParcelFileDescriptor fd = openFileDescriptor(uri, "rw");
+            return fd != null
+                    ? new ParcelFileDescriptor.AutoCloseOutputStream(fd) : null;
+        } else {
+            throw new FileNotFoundException("Unknown scheme: " + uri);
+        }
+    }
+
+    /**
+     * Open a raw file descriptor to access data under a "content:" URI.  This
+     * interacts with the underlying {@link ContentProvider#openFile}
+     * ContentProvider.openFile()} 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>
+     * </ul>
+     *
+     * @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.
+     */
+    public final ParcelFileDescriptor openFileDescriptor(Uri uri,
+            String mode) throws FileNotFoundException {
+        IContentProvider provider = acquireProvider(uri);
+        if (provider == null) {
+            throw new FileNotFoundException("No content provider: " + uri);
+        }
+        try {
+            ParcelFileDescriptor fd = provider.openFile(uri, mode);
+            if(fd == null) {
+                releaseProvider(provider);
+                return null;
+            }
+            return new ParcelFileDescriptorInner(fd, provider);
+        } 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;
+        }
+    }
+
+    /**
+     * 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..f050501
--- /dev/null
+++ b/core/java/android/content/ContentServiceNative.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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..00a6d31
--- /dev/null
+++ b/core/java/android/content/Context.java
@@ -0,0 +1,1631 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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.
+     * </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.internal.TelephonyManager
+     */
+    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";
+
+    /**
+     * 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..7dc71b8
--- /dev/null
+++ b/core/java/android/content/DefaultDataHandler.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * insert 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
+ * Samples:
+ *  insert one row
+ * <row uri="content://contacts/people">
+ *  <Col column = "name" value = "foo feebe "/>
+ *  <Col column = "addr" value = "Tx"/>
+ * </row>
+ * 
+ * delete, it must be in order of uri, select and arg
+ * <del uri="content://contacts/people" select="name=? and addr=?" 
+ *  arg1 = "foo feebe" arg2 ="Tx"/>
+ *
+ *  use first row's uri to insert into another table
+ *  content://contacts/people/1/phones
+ * <row uri="content://contacts/people">
+ *  <col column = "name" value = "foo feebe"/>
+ *  <col column = "addr" value = "Tx"/>
+ *  <row postfix="phones">
+ *    <col column="number" value="512-514-6535"/>
+ *  </row>
+ *  <row postfix="phones">
+ *    <col column="cell" value="512-514-6535"/>
+ *  </row>  
+ * </row>
+ * 
+ *  insert multiple rows in to same table and same attributes:
+ * <row uri="content://contacts/people" >
+ *  <row>
+ *   <col column= "name" value = "foo feebe"/>
+ *   <col column= "addr" value = "Tx"/>
+ *  </row>
+ *  <row>
+ *  </row>
+ * </row> 
+ * 
+ * @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..fc94aa6
--- /dev/null
+++ b/core/java/android/content/DialogInterface.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.content;
+
+import android.view.KeyEvent;
+
+/**
+ * 
+ */
+public interface DialogInterface {    
+    public static final int BUTTON1 = -1;
+    public static final int BUTTON2 = -2;
+    public static final int BUTTON3 = -3;
+
+    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, i.e. BUTTON1 or BUTTON2 or
+         *            the position of the item clicked.
+         */
+        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/Formatter.java b/core/java/android/content/Formatter.java
new file mode 100644
index 0000000..8ad9f40
--- /dev/null
+++ b/core/java/android/content/Formatter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * Utility class to aid in formatting common values that are not covered
+ * by the standard java.util.Formatter
+ * @hide
+ */
+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());
+    }
+}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
new file mode 100644
index 0000000..a6ef46f
--- /dev/null
+++ b/core/java/android/content/IContentProvider.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.content;
+
+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 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;
+}
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..c76158c
--- /dev/null
+++ b/core/java/android/content/Intent.java
@@ -0,0 +1,4391 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.util.TypedValue;
+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_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_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}.
+ *
+ * <ul>
+ *     <li> {@link #FLAG_GRANT_READ_URI_PERMISSION}
+ *     <li> {@link #FLAG_GRANT_WRITE_URI_PERMISSION}
+ *     <li> {@link #FLAG_FROM_BACKGROUND}
+ *     <li> {@link #FLAG_DEBUG_LOG_RESOLUTION}
+ *     <li> {@link #FLAG_ACTIVITY_NO_HISTORY}
+ *     <li> {@link #FLAG_ACTIVITY_SINGLE_TOP}
+ *     <li> {@link #FLAG_ACTIVITY_NEW_TASK}
+ *     <li> {@link #FLAG_ACTIVITY_MULTIPLE_TASK}
+ *     <li> {@link #FLAG_ACTIVITY_FORWARD_RESULT}
+ *     <li> {@link #FLAG_ACTIVITY_PREVIOUS_IS_TOP}
+ *     <li> {@link #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS}
+ *     <li> {@link #FLAG_ACTIVITY_BROUGHT_TO_FRONT}
+ *     <li> {@link #FLAG_RECEIVER_REGISTERED_ONLY}
+ * </ul>
+ */
+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>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).
+     * @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 icon resource.
+     *
+     * @see Intent#ACTION_CREATE_SHORTCUT
+     * @see Intent#EXTRA_SHORTCUT_ICON_RESOURCE
+     */
+    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 #getData} is URI of data. 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.
+     */
+    @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.
+     */
+    @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.
+     */
+    @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, all runtime state
+     * associated with it (processes, alarms, notifications, etc) should
+     * be remove.  The data contains the name of the package.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+    /**
+     * 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 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";
+
+    /**
+     * <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";
+    /**
+     * 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 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";
+
+    // ---------------------------------------------------------------------
+    // ---------------------------------------------------------------------
+    // 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.
+     */
+    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 the
+     * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
+     * documentation 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 the
+     * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
+     * documentation 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 receiving the new intent you are starting here in its
+     * onNewIntent() method, or be itself finished and restarting 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 the
+     * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
+     * documentation 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;
+    /**
+     * If set, this activity is being launched from history (longpress home key).
+     */
+    public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000;
+
+    /**
+     * 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;
+
+    // ---------------------------------------------------------------------
+
+    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;
+    }
+
+    /**
+     * 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}intro/appmodel.html">Application Model</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_RESET_TASK_IF_NEEDED
+     * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
+     * @see #FLAG_ACTIVITY_CLEAR_TOP
+     * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+     * @see #FLAG_ACTIVITY_FORWARD_RESULT
+     * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+     * @see #FLAG_ACTIVITY_NEW_TASK
+     * @see #FLAG_ACTIVITY_NO_HISTORY
+     * @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) {
+            Intent other;
+            try {
+                other = ((FilterComparison)obj).mIntent;
+            } catch (ClassCastException e) {
+                return false;
+            }
+
+            return mIntent.filterEquals(other);
+        }
+
+        @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")) {
+                parseExtra(resources, intent, parser, attrs);
+
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        return intent;
+    }
+
+    private static void parseExtra(Resources resources, Intent intent, XmlPullParser parser,
+            AttributeSet attrs) throws XmlPullParserException, IOException {
+        TypedArray sa = resources.obtainAttributes(attrs,
+                com.android.internal.R.styleable.IntentExtra);
+
+        String name = sa.getString(
+                com.android.internal.R.styleable.IntentExtra_name);
+        if (name == null) {
+            sa.recycle();
+            throw new RuntimeException(
+                    "<extra> requires an android:name attribute at "
+                    + parser.getPositionDescription());
+        }
+
+        TypedValue v = sa.peekValue(
+                com.android.internal.R.styleable.IntentExtra_value);
+        if (v != null) {
+            if (v.type == TypedValue.TYPE_STRING) {
+                CharSequence cs = v.coerceToString();
+                intent.putExtra(name, cs != null ? cs.toString() : null);
+            } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+                intent.putExtra(name, v.data != 0);
+            } else if (v.type >= TypedValue.TYPE_FIRST_INT
+                    && v.type <= TypedValue.TYPE_LAST_INT) {
+                intent.putExtra(name, v.data);
+            } else if (v.type == TypedValue.TYPE_FLOAT) {
+                intent.putExtra(name, v.getFloat());
+            } else {
+                sa.recycle();
+                throw new RuntimeException(
+                        "<extra> only supports string, integer, float, color, and boolean at "
+                        + parser.getPositionDescription());
+            }
+        } else {
+            sa.recycle();
+            throw new RuntimeException(
+                    "<extra> requires an android:value or android:resource attribute at "
+                    + parser.getPositionDescription());
+        }
+
+        sa.recycle();
+
+        XmlUtils.skipCurrentTag(parser);
+    }
+}
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..2bf84e7
--- /dev/null
+++ b/core/java/android/content/SyncManager.java
@@ -0,0 +1,2170 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.pim.DateUtils;
+import android.pim.Time;
+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.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();
+            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 && (!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;
+            throw new IllegalStateException("we are not in an error state, " + toString());
+        }
+
+        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..1e55e27
--- /dev/null
+++ b/core/java/android/content/SyncableContentProvider.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.accounts.AccountMonitor;
+import android.accounts.AccountMonitorListener;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.provider.SyncConstValue;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+/**
+ * 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 {
+    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 SyncableContentProvider
+     * @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 SyncableContentProvider(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 SyncableContentProvider.
+     */
+    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 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 SyncableContentProvider getTemporaryInstance() {
+        SyncableContentProvider 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
+     * 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 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 == values.length) {
+            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(
+                        diffs.mOpenHelper.getReadableDatabase(),
+                        mOpenHelper.getWritableDatabase(),
+                        getSyncingAccount());
+                }
+            } finally {
+                synchronized (this) {
+                    mCurrentMerger = null;
+                }
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+
+    /**
+     * 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 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 {
+            // remote 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 bytes array may be null.
+     */
+    public void writeSyncDataBytes(String account, byte[] data) {
+        mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
+    }
+}
+
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
new file mode 100644
index 0000000..78510aa
--- /dev/null
+++ b/core/java/android/content/TempProviderSyncAdapter.java
@@ -0,0 +1,546 @@
+package android.content;
+
+import com.google.android.net.NetStats;
+
+import android.database.SQLException;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Parcelable;
+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[] mNetStats;
+        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.
+            long[] newNetStats = NetStats.getStats();
+            logSyncDetails(newNetStats[0] - mNetStats[0], newNetStats[1] - mNetStats[1], mResult);
+        }
+        
+        @Override
+        public void run() {
+            android.os.Process.setThreadPriority(android.os.Process.myTid(),
+                    android.os.Process.THREAD_PRIORITY_BACKGROUND);
+            mNetStats = NetStats.getStats();
+            try {
+                sync(mSyncContext, mAccount, mExtras);
+            } catch (SQLException e) {
+                Log.e(TAG, "Sync failed", e);
+                mResult.databaseError = true;
+            } finally {
+                mSyncThread = null;
+                if (!mIsCanceled) {
+                    long[] newNetStats = NetStats.getStats();
+                    logSyncDetails(newNetStats[0] - mNetStats[0], newNetStats[1] - mNetStats[1], 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..7b3e8cf
--- /dev/null
+++ b/core/java/android/content/package.html
@@ -0,0 +1,649 @@
+<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}devel/resources-i18n.html">Resources
+    and Internationalization</a>.  For a reference on the supported Android resource types,
+    see <a href="{@docRoot}reference/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}devel/resources-i18n.html">Resources</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..f577d2d
--- /dev/null
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -0,0 +1,332 @@
+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;
+    /**
+     * 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}.
+     */
+    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;
+    
+    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;
+    }
+    
+    /**
+     * 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)
+                + " orien=" + screenOrientation
+                + " configChanges=0x" + Integer.toHexString(configChanges));
+        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);
+    }
+
+    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();
+    }
+}
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..22d01dc
--- /dev/null
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -0,0 +1,303 @@
+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;
+
+    /**
+     * 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, sb);
+        }
+
+        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/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..c79655d
--- /dev/null
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -0,0 +1,230 @@
+/*
+**
+** 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;
+
+/**
+ *  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);
+    
+    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 gurantee 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 callback used to notify when the operation is completed
+     */
+     void freeApplicationCache(in long freeStorageSize, IPackageDataObserver observer);
+     
+    /**
+     * 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);
+
+    void enterSafeMode();
+    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..e6745da
--- /dev/null
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -0,0 +1,96 @@
+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);
+    }
+
+    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;
+    }
+}
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..7d694c7
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -0,0 +1,170 @@
+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;
+    
+    /**
+     * 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;
+
+    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);
+        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);
+    }
+
+    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();
+        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);
+    }
+}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
new file mode 100644
index 0000000..406a3eb
--- /dev/null
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -0,0 +1,188 @@
+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;
+            }
+        }
+        return name;
+    }
+    
+    /**
+     * 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, sb);
+        }
+
+        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..db00a9a
--- /dev/null
+++ b/core/java/android/content/pm/PackageManager.java
@@ -0,0 +1,1453 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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;
+
+    /**
+     * 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 Optional flags to control what information is returned.  If
+     *              0, none of the optional information is returned.
+     *
+     * @return Returns a PackageInfo containing information about the package.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_INSTRUMENTATION
+     * @see #GET_SIGNATURES
+     */
+    public abstract PackageInfo getPackageInfo(String packageName, int flags)
+            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.  Currently should always be 0.
+     *
+     * @return {@link ApplicationInfo} containing information about the
+     *         application.
+     */
+    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.  Usually 0.
+     *
+     * @return {@link ActivityInfo} containing information about the activity.
+     *
+     * @see #GET_INTENT_FILTERS
+     */
+    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.  Usually 0.
+     *
+     * @return {@link ActivityInfo} containing information about the receiver.
+     *
+     * @see #GET_INTENT_FILTERS
+     */
+    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.  Currently should always be 0.
+     *
+     * @return ServiceInfo containing information about the service.
+     */
+    public abstract ServiceInfo getServiceInfo(ComponentName className,
+            int flags) throws NameNotFoundException;
+
+    /**
+     * Return a List of all packages that are installed
+     * on the device.
+     *
+     * @param flags Optional flags to control what information is returned.  If
+     *              0, none of the optional information is 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.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_INSTRUMENTATION
+     * @see #GET_SIGNATURES
+     */
+    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 a List of all application packages that are installed on the
+     * device.
+     *
+     * @param flags Additional option flags.  Currently should always be 0.
+     *
+     * @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.
+     */
+    public abstract List<ApplicationInfo> getInstalledApplications(int flags);
+
+    /**
+     * 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 Optional flags to control what information is returned.  If
+     *              0, none of the optional information is returned.
+     *
+     * @return Returns the information about the package. Returns
+     * null if the package could not be successfully parsed.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_INSTRUMENTATION
+     * @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 gurantee 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 callback used to notify when the operation is completed
+     * {@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 freeApplicationCache(long freeStorageSize,
+            IPackageDataObserver observer);
+
+    /**
+     * 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 Optional flags to control what information is returned.  If
+     *              0, none of the optional information is returned.
+     *
+     * @return Returns a list of PackageInfo objects describing each
+     * preferred application, in order of preference.
+     *
+     * @see #GET_ACTIVITIES
+     * @see #GET_RECEIVERS
+     * @see #GET_SERVICES
+     * @see #GET_INSTRUMENTATION
+     * @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);
+}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
new file mode 100644
index 0000000..5a90261
--- /dev/null
+++ b/core/java/android/content/pm/PackageParser.java
@@ -0,0 +1,2287 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.applicationInfo = p.applicationInfo;
+        if ((flags&PackageManager.GET_GIDS) != 0) {
+            pi.gids = gids;
+        }
+        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;
+        try {
+            assmgr = new AssetManager();
+            assmgr.addAssetPath(mArchiveSourcePath);
+            parser = assmgr.openXmlResourceParser("AndroidManifest.xml");
+        } catch (Exception e) {
+            if (assmgr != null) assmgr.close();
+            Log.w(TAG, "Unable to read AndroidManifest.xml of "
+                    + mArchiveSourcePath, e);
+            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();
+        }
+        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-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 (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;
+        } else if (ai.processName != null && !ai.processName.equals(ai.packageName)
+                && ai.className != null) {
+            Log.w(TAG, "In package " + ai.packageName
+                    + " <application> specifies both a name and a process; ignoring the process");
+            ai.processName = null;
+        }
+
+        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_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);
+        } 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;
+
+        // 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;
+
+        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..4a073f7
--- /dev/null
+++ b/core/java/android/content/res/AssetFileDescriptor.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.content.res;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+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 {
+    private final ParcelFileDescriptor mFd;
+    private final long mStartOffset;
+    private final long mLength;
+    
+    /**
+     * Create a new AssetFileDescriptor from the given values.
+     */
+    public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
+            long 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.
+     */
+    public long getLength() {
+        return mLength;
+    }
+    
+    /**
+     * Convenience for calling <code>getParcelFileDescriptor().close()</code>.
+     */
+    public void close() throws IOException {
+        mFd.close();
+    }
+}
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..ee88c89
--- /dev/null
+++ b/core/java/android/content/res/ColorStateList.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ *
+ * 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");
+        }
+
+        final ColorStateList colorStateList = createFromXmlInner(r, parser, attrs);
+
+        return colorStateList;
+    }
+
+    /* 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) {
+        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) {
+            Object[] o = source.readArray(
+                                    ColorStateList.class.getClassLoader());
+            int[][] stateSpecs = new int[o.length][];
+
+            for (int i = 0; i < o.length; i++) {
+                stateSpecs[i] = (int[]) o[i];
+            }
+
+            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..78a90de
--- /dev/null
+++ b/core/java/android/content/res/Configuration.java
@@ -0,0 +1,386 @@
+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;
+
+    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;
+    
+    /**
+     * A flag indicating whether the 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.
+     */
+    public int keyboardHidden;
+    
+    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();
+        }
+        touchscreen = o.touchscreen;
+        keyboard = o.keyboard;
+        keyboardHidden = o.keyboardHidden;
+        navigation = o.navigation;
+        orientation = o.orientation;
+    }
+
+    public String toString() {
+        return "{ scale=" + fontScale + " imsi=" + mcc + "/" + mnc
+                + " locale=" + locale
+                + " touch=" + touchscreen + " key=" + keyboard + "/"
+                + keyboardHidden
+                + " nav=" + navigation + " orien=" + orientation + " }";
+    }
+
+    /**
+     * Set this object to the system defaults.
+     */
+    public void setToDefaults() {
+        fontScale = 1;
+        mcc = mnc = 0;
+        locale = Locale.getDefault();
+        touchscreen = TOUCHSCREEN_UNDEFINED;
+        keyboard = KEYBOARD_UNDEFINED;
+        keyboardHidden = KEYBOARDHIDDEN_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.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.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.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());
+        }
+        dest.writeInt(touchscreen);
+        dest.writeInt(keyboard);
+        dest.writeInt(keyboardHidden);
+        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());
+        }
+        touchscreen = source.readInt();
+        keyboard = source.readInt();
+        keyboardHidden = 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.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.navigation
+                + this.orientation;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/PluralRules.java b/core/java/android/content/res/PluralRules.java
new file mode 100644
index 0000000..2d09ef0
--- /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 PluralRule {
+
+    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 PluralRule.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 PluralRule 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 PluralRule cs;
+    private static class cs extends PluralRule {
+        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 PluralRule en;
+    private static class en extends PluralRule {
+        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..1014eee
--- /dev/null
+++ b/core/java/android/content/res/Resources.java
@@ -0,0 +1,1659 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
+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.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 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 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();
+    PluralRule 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 {
+        PluralRule rule = getPluralRule();
+        CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity));
+        if (res != null) {
+            return res;
+        }
+        res = mAssets.getResourceBagText(id, PluralRule.ID_OTHER);
+        if (res != null) {
+            return res;
+        }
+        throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
+                + " quantity=" + quantity
+                + " item=" + PluralRule.stringForQuantity(rule.quantityForNumber(quantity)));
+    }
+
+    private PluralRule getPluralRule() {
+        synchronized (mSync) {
+            if (mPluralRule == null) {
+                mPluralRule = PluralRule.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.
+     *
+     * @return CharSequence The string data associated with the resource, plus
+     * @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.
+     *
+     * @return CharSequence The string data associated with the resource, plus
+     * @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.
+     *
+     * @return CharSequence The string data associated with the resource, plus
+     * @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");
+        }
+    }
+
+    /**
+     * 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 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) {
+            TypedValue value = mTmpValue;
+            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 {
+                width = mMetrics.heightPixels;
+                height = mMetrics.widthPixels;
+            }
+            mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
+                    locale, mConfiguration.orientation,
+                    mConfiguration.touchscreen,
+                    (int)(mMetrics.density*160), mConfiguration.keyboard,
+                    mConfiguration.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 = PluralRule.ruleForLocale(config.locale);
+            }
+        }
+    }
+
+    /**
+     * 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) {
+        }
+        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));
+    }
+    
+    /**
+     * 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 (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+            && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+            // Should we be caching these?  If we use constant colors much
+            // at all, most likely...
+            //System.out.println("Creating drawable for color: #" +
+            //                   Integer.toHexString(value.data));
+            Drawable dr = new ColorDrawable(value.data);
+            dr.setChangingConfigurations(value.changingConfigurations);
+            return dr;
+        }
+
+        final int key = (value.assetCookie<<24)|value.data;
+        Drawable dr = getCachedDrawable(key);
+        //System.out.println("Cached drawable @ #" +
+        //                   Integer.toHexString(key.intValue()) + ": " + dr);
+        if (dr != null) {
+            return dr;
+        }
+
+        Drawable.ConstantState cs = mPreloadedDrawables.get(key);
+        if (cs != null) {
+            dr = cs.newDrawable();
+            
+        } else {
+            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.createFromStream(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);
+                }
+                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 final 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 (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+            && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+            return ColorStateList.valueOf(value.data);
+        }
+
+        final int key = (value.assetCookie<<24)|value.data;
+        ColorStateList csl = getCachedColorStateList(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) {
+            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..da32cc8
--- /dev/null
+++ b/core/java/android/content/res/StringBlock.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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");
+
+                    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 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) {
+                buffer.setSpan(new BulletSpan(10),
+                               style[i+1], style[i+2]+1,
+                               Spannable.SPAN_PARAGRAPH);
+            } else {
+                String tag = nativeGetString(mNative, type);
+
+                if (tag.startsWith("font;")) {
+                    String sub;
+
+                    sub = subtag(tag, ";height=");
+                    if (sub != null) {
+                        int size = Integer.parseInt(sub);
+                        buffer.setSpan(new Height(size),
+                                       style[i+1], style[i+2]+1,
+                                       Spannable.SPAN_PARAGRAPH);
+                    }
+
+                    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);
+                    }
+                }
+            }
+
+            i += 3;
+        }
+        return new SpannedString(buffer);
+    }
+
+    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..82a57dd
--- /dev/null
+++ b/core/java/android/content/res/TypedArray.java
@@ -0,0 +1,660 @@
+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.");
+    }
+
+    /**
+     * 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..e81f7f8
--- /dev/null
+++ b/core/java/android/database/AbstractCursor.java
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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);
+        }
+    }
+
+    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..1ec4312
--- /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("This cursor has changed, you must call requery()");
+        }
+    }
+
+    @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..72dc3a9
--- /dev/null
+++ b/core/java/android/database/CursorWindow.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.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() {
+        mStartPos = 0;
+        native_clear();
+    }
+
+    /** 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..ab0dc3f7
--- /dev/null
+++ b/core/java/android/database/DatabaseUtils.java
@@ -0,0 +1,1002 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+            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());
+        }
+    }
+    
+    /**
+     * 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..ae2fc95
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCursor.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.database.sqlite;
+
+import android.database.AbstractWindowedCursor;
+import android.database.CursorWindow;
+import android.database.SQLException;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 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;
+
+    /**
+     * 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 {
+            mWindow.clear();
+        }
+
+        // mWindow must be cleared
+        mCount = mQuery.fillWindow(mWindow, startPos);
+    }
+
+    @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);
+        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() {
+        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;
+            // Requery the program that runs over the temp table
+            mQuery.requery();
+        } 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) {
+            mWindow.close();
+            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..e497190
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -0,0 +1,1512 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 final static String TAG = "Database";
+
+    /**
+     * 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;
+    /**
+     * @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 {
+                execSQL("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
+     */
+    public boolean yieldIfContended() {
+        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();
+        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 maximum size the database may grow to.
+     *
+     * @return the new maximum database size
+     */
+    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>"));
+            }
+        }
+    }
+
+    /**
+     * 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 insertOrReplace(table, nullColumnHack, values, false);
+        } 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 insertOrReplace(table, nullColumnHack, values, false) ;
+    }
+
+    /**
+     * 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 insertOrReplace(table, nullColumnHack, initialValues, true);
+        } 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 insertOrReplace(table, nullColumnHack, initialValues, true);
+    }
+
+    private long insertOrReplace(String table, String nullColumnHack,
+            ContentValues initialValues, boolean allowReplace) {
+        if (!isOpen()) {
+            throw new IllegalStateException("database not open");
+        }
+
+        // Measurements show most sql lengths <= 152
+        StringBuilder sql = new StringBuilder(152);
+        sql.append("INSERT ");
+        if (allowReplace) {
+            sql.append("OR REPLACE ");
+        }
+        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) {
+        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 ");
+        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 {
+        long timeStart = 0;
+        if (Config.LOGV) {
+            timeStart = System.currentTimeMillis();
+        }
+        lock();
+        try {
+            native_execSQL(sql);
+        } catch (SQLiteDatabaseCorruptException e) {
+            onCorruption();
+            throw e;
+        } finally {
+            unlock();
+        }
+        if (Config.LOGV) {
+            long timeEnd = System.currentTimeMillis();
+            Log.v(TAG, "Executed (" + (timeEnd - timeStart) + " ms):" + sql);
+        }
+    }
+
+    /**
+     * 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");
+        }
+        long timeStart = 0;
+        if (Config.LOGV) {
+            timeStart = System.currentTimeMillis();
+        }
+        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 (Config.LOGV) {
+            long timeEnd = System.currentTimeMillis();
+            Log.v(TAG, "Executed (" + (timeEnd - timeStart) + " ms):" + sql);
+        }
+    }
+
+    @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 createDatabase} 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;
+        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;
+    }
+
+    /**
+     * 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 mLock} 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 mLock} 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..f6872ac
--- /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.
+ *
+ * @see com.google.provider.NotePad.NotePadProvider
+ */
+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..e0341a2
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteProgram.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.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);
+                }
+            }
+            onAllReferencesReleased();
+        }
+    }
+
+    /**
+     * 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..40855b6
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteQuery.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.database.sqlite;
+
+import android.database.CursorWindow;
+
+/**
+ * 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. It must include "INDEX -1
+     *            OFFSET ?" at the end
+     * @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
+     * @param startPos The position to start reading rows from
+     * @return number of total rows in the query
+     */
+    /* package */ int fillWindow(CursorWindow window, int startPos) {
+        if (startPos < 0) {
+            throw new IllegalArgumentException("startPos should > 0");
+        }
+        window.setStartPosition(startPos);
+        mDatabase.lock();
+        try {
+            acquireReference();
+            window.acquireReference();
+            return native_fill_window(window, startPos, mOffsetIndex);
+        } catch (IllegalStateException e){
+            // simply ignore it
+            return 0;
+        } catch (SQLiteDatabaseCorruptException e) {
+            mDatabase.onCorruption();
+            throw e;
+        } finally {
+            window.releaseReference();
+            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();
+        }
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        mClosed = true;
+    }
+
+    /**
+     * Called by SQLiteCursor when it is requeried.
+     */
+    /* package */ void requery() {
+        boolean oldMClosed = mClosed;
+        if (mClosed) {
+            mClosed = false;
+            compile(mQuery, false);
+        }
+        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
+                    ("old mClosed " + oldMClosed + " 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);
+
+    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..bf9361d
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteStatement.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.database.sqlite;
+
+/**
+ * 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
+{
+    /**
+     * 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);
+    }
+
+    /**
+     * 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();
+        acquireReference();
+        try {
+            native_execute();
+        } 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();
+        acquireReference();
+        try {
+            native_execute();
+            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();
+        acquireReference();
+        try {
+            return native_1x1_long();
+        } 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();
+        acquireReference();
+        try {
+            return native_1x1_string();
+        } 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..c03a8dc
--- /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}devel/data.html">Storing, Retrieving and Exposing Data</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/hardware/Camera.java b/core/java/android/hardware/Camera.java
new file mode 100644
index 0000000..8330750
--- /dev/null
+++ b/core/java/android/hardware/Camera.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 int mListenerContext;
+    private EventHandler mEventHandler;
+    private ShutterCallback mShutterCallback;
+    private PictureCallback mRawImageCallback;
+    private PictureCallback mJpegCallback;
+    private PreviewCallback mPreviewCallback;
+    private AutoFocusCallback mAutoFocusCallback;
+    private ErrorCallback mErrorCallback;
+    
+    /**
+     * 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();
+    }
+
+    /**
+     * 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
+     */
+    public final void setPreviewDisplay(SurfaceHolder holder) {
+        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();
+    
+    /**
+     * 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;
+        setHasPreviewCallback(cb != null);
+    }
+    private native final void setHasPreviewCallback(boolean installed);
+
+    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);
+                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);
+    };
+
+    /**
+     * Registers a callback to be invoked when the auto focus responds.
+     * 
+     * @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);
+    };
+
+    /**
+     * Registers a callback to be invoked when a picture is taken.
+     * 
+     * @param raw  the callback to run for raw images, may be null
+     * @param jpeg the callback to run for jpeg images, 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();
+            String[] pairs = flattened.split(";");
+            for (String p : pairs) {
+                String[] kv = p.split("=");
+                if (kv.length == 2)
+                    mMap.put(kv[0], kv[1]);
+            }
+        }
+        
+        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 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_422_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_422_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.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("rgb565"))
+                return PixelFormat.RGB_565;
+
+            if (format.equals("jpeg"))
+                return PixelFormat.JPEG;
+
+            return PixelFormat.UNKNOWN;
+        }
+
+    };
+}
+
+
diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/hardware/ISensorService.aidl
new file mode 100644
index 0000000..b6ac3ab
--- /dev/null
+++ b/core/java/android/hardware/ISensorService.aidl
@@ -0,0 +1,30 @@
+/* //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, int sensor, int enable);
+    oneway void reportAccuracy(int sensor, int value);
+}
diff --git a/core/java/android/hardware/SensorListener.java b/core/java/android/hardware/SensorListener.java
new file mode 100644
index 0000000..d676a5e
--- /dev/null
+++ b/core/java/android/hardware/SensorListener.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.hardware;
+
+/**
+ * Used for receiving notifications from the SensorManager when
+ * sensor values have changed.
+ */
+public interface SensorListener {
+
+    /**
+     * 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 and values.
+     *
+     * @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..9b88fff
--- /dev/null
+++ b/core/java/android/hardware/SensorManager.java
@@ -0,0 +1,619 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.Arrays;
+
+/**
+ * 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";
+
+    /** NOTE: sensor IDs must be a power of 2 */
+
+    /** A constant describing an orientation sensor.
+     * Sensor values are yaw, pitch and roll
+     *
+     * Yaw is the compass heading in degrees, range [0, 360[
+     * 0 = North, 90 = East, 180 = South, 270 = West
+     *
+     * Pitch indicates the tilt of the top of the device,
+     * with range -90 to 90.
+     * Positive values indicate that the bottom of the device is tilted up
+     * and negative values indicate the top of the device is tilted up.
+     *
+     * Roll indicates the side to side tilt of the device,
+     * with range -90 to 90.
+     * Positive values indicate that the left side of the device is tilted up
+     * and negative values indicate the right side of the device is tilted up.
+     */
+    public static final int SENSOR_ORIENTATION = 1 << 0;
+
+    /** A constant describing an accelerometer.
+     * Sensor values are acceleration in the X, Y and Z axis,
+     * where the X axis has positive direction toward the right side of the device,
+     * the Y axis has positive direction toward the top of the device
+     * and the Z axis has positive direction toward the front of the device.
+     *
+     * The direction of the force of gravity is indicated by acceleration values in the
+     * X, Y and Z axes.  The typical case where the device is flat relative to the surface
+     * of the Earth appears as -STANDARD_GRAVITY in the Z axis
+     * and X and Z values close to zero.
+     *
+     * Acceleration values are given in SI units (m/s^2)
+     *
+     */
+    public static final int SENSOR_ACCELEROMETER = 1 << 1;
+
+    /** A constant describing a temperature sensor
+     * Only the first value is defined for this sensor and it
+     * contains the ambient temperature in degree C.
+     */
+    public static final int SENSOR_TEMPERATURE = 1 << 2;
+
+    /** A constant describing a magnetic sensor
+     * Sensor values are the magnetic vector in the X, Y and Z axis,
+     * where the X axis has positive direction toward the right side of the device,
+     * the Y axis has positive direction toward the top of the device
+     * and the Z axis has positive direction toward the front of the device.
+     * 
+     * Magnetic values are given in micro-Tesla (uT)
+     * 
+     */
+    public static final int SENSOR_MAGNETIC_FIELD = 1 << 3;
+
+    /** A constant describing an ambient light sensor
+     * Only the first value is defined for this sensor and it contains
+     * the ambient light measure in lux.
+     * 
+     */
+    public static final int SENSOR_LIGHT = 1 << 4;
+
+    /** A constant describing a proximity sensor
+     * Only the first value is defined for this sensor and it contains
+     * the distance between the sensor and the object in meters (m)
+     */
+    public static final int SENSOR_PROXIMITY = 1 << 5;
+
+    /** A constant describing a Tricorder
+     * When this sensor is available and enabled, the device can be
+     * used as a fully functional Tricorder. All values are returned in 
+     * SI units.
+     */
+    public static final int SENSOR_TRICORDER = 1 << 6;
+
+    /** A constant describing an orientation sensor.
+     * Sensor values are yaw, pitch and roll
+     *
+     * Yaw is the compass heading in degrees, 0 <= range < 360
+     * 0 = North, 90 = East, 180 = South, 270 = West
+     *
+     * This is similar to SENSOR_ORIENTATION except the data is not 
+     * smoothed or filtered in any way.
+     */
+    public static final int SENSOR_ORIENTATION_RAW = 1 << 7;
+
+    /** A constant that includes all sensors */
+    public static final int SENSOR_ALL = 0x7F;
+
+    /** Smallest sensor ID */
+    public static final int SENSOR_MIN = SENSOR_ORIENTATION;
+
+    /** Largest sensor ID */
+    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} */
+    public static final int DATA_X = 0;
+    /** Index of the Y value in the array returned by 
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    public static final int DATA_Y = 1;
+    /** Index of the Z value in the array returned by 
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    public static final int DATA_Z = 2;
+    
+    /** Offset to the raw values in the array returned by 
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    public static final int RAW_DATA_INDEX = 3;
+
+    /** Index of the raw X value in the array returned by 
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    public static final int RAW_DATA_X = 3;
+    /** Index of the raw X value in the array returned by 
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    public static final int RAW_DATA_Y = 4;
+    /** Index of the raw X value in the array returned by 
+     * {@link android.hardware.SensorListener#onSensorChanged} */
+    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_NEPTUN          = 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;
+
+    
+
+    private static final int SENSOR_DISABLE = -1;
+    private static final int SENSOR_ORDER_MASK = 0x1F;
+    private static final int SENSOR_STATUS_SHIFT = 28;
+    private ISensorService mSensorService;
+    private Looper mLooper;
+
+    private static IWindowManager sWindowManager;
+    private static int sRotation = 0;
+
+    /* The thread and the sensor list are global to the process 
+     * but the actual thread is spawned on demand */
+    static final private SensorThread sSensorThread = new SensorThread();
+    static final private ArrayList<ListenerDelegate> sListeners = 
+        new ArrayList<ListenerDelegate>();
+
+
+    static private class SensorThread {
+
+        private Thread mThread;
+
+        // must be called with sListeners lock
+        void startLocked(ISensorService service) {
+            try {
+                if (mThread == null) {
+                    ParcelFileDescriptor fd = service.getDataChanel();
+                    mThread = new Thread(new SensorThreadRunnable(fd, service), 
+                            SensorThread.class.getName());
+                    mThread.start();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in startLocked: ", e);
+            }
+        }
+
+        private class SensorThreadRunnable implements Runnable {
+            private ISensorService mSensorService;
+            private ParcelFileDescriptor mSensorDataFd;
+            private final byte mAccuracies[] = new byte[32];
+            SensorThreadRunnable(ParcelFileDescriptor fd, ISensorService service) {
+                mSensorDataFd = fd;
+                mSensorService = service;
+                Arrays.fill(mAccuracies, (byte)-1);
+            }
+            public void run() {
+                int sensors_of_interest;
+                float[] values = new float[6];
+                Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
+
+                synchronized (sListeners) {
+                    _sensors_data_open(mSensorDataFd.getFileDescriptor());
+                    try {
+                        mSensorDataFd.close();
+                    } catch (IOException e) {
+                        // *shrug*
+                        Log.e(TAG, "IOException: ", e);
+                    }
+                    mSensorDataFd = null;
+                    //mSensorDataFd.
+                    // the first time, compute the sensors we need. this is not
+                    // a big deal if it changes by the time we call
+                    // _sensors_data_poll, it'll get recomputed for the next
+                    // round.
+                    sensors_of_interest = 0;
+                    final int size = sListeners.size();
+                    for (int i=0 ; i<size ; i++) {
+                        sensors_of_interest |= sListeners.get(i).mSensors;
+                        if ((sensors_of_interest & SENSOR_ALL) == SENSOR_ALL)
+                            break;
+                    }
+                }
+
+                while (true) {
+                    // wait for an event
+                    final int sensor_result = _sensors_data_poll(values, sensors_of_interest);
+                    final int sensor_order = sensor_result & SENSOR_ORDER_MASK;
+                    final int sensor = 1 << sensor_result;
+                    int accuracy = sensor_result>>>SENSOR_STATUS_SHIFT;
+
+                    if ((sensors_of_interest & sensor)!=0) {
+                        // show the notification only if someone is listening for
+                        // this sensor
+                        if (accuracy != mAccuracies[sensor_order]) {
+                            try {
+                                mSensorService.reportAccuracy(sensor, accuracy);
+                                mAccuracies[sensor_order] = (byte)accuracy;
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "RemoteException in reportAccuracy: ", e);
+                            }
+                        } else {
+                            accuracy = -1;
+                        }
+                    }
+                    
+                    synchronized (sListeners) {
+                        if (sListeners.isEmpty()) {
+                            // we have no more listeners, terminate the thread
+                            _sensors_data_close();
+                            mThread = null;
+                            break;
+                        }
+                        // convert for the current screen orientation
+                        mapSensorDataToWindow(sensor, values, SensorManager.getRotation());
+                        // report the sensor event to all listeners that
+                        // care about it.
+                        sensors_of_interest = 0;
+                        final int size = sListeners.size();
+                        for (int i=0 ; i<size ; i++) {
+                            ListenerDelegate listener = sListeners.get(i);
+                            sensors_of_interest |= listener.mSensors;
+                            if (listener.hasSensor(sensor)) {
+                                // this is asynchronous (okay to call
+                                // with sListeners lock held.
+                                listener.onSensorChanged(sensor, values, accuracy);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private class ListenerDelegate extends Binder {
+
+        private SensorListener mListener;
+        private int mSensors;
+        private float[] mValuesPool;
+
+        ListenerDelegate(SensorListener listener, int sensors) {
+            mListener = listener;
+            mSensors = sensors;
+            mValuesPool = new float[6];
+        }
+
+        int addSensors(int sensors) {
+            mSensors |= sensors;
+            return mSensors;
+        }
+        int removeSensors(int sensors) {
+            mSensors &= ~sensors;
+            return mSensors;
+        }
+        boolean hasSensor(int sensor) {
+            return ((mSensors & sensor) != 0);
+        }
+
+        void onSensorChanged(int sensor, float[] values, int accuracy) {
+            float[] v;
+            synchronized (this) {
+                // remove the array from the pool
+                v = mValuesPool;
+                mValuesPool = null;
+            }
+
+            if (v != null) {
+                v[0] = values[0];
+                v[1] = values[1];
+                v[2] = values[2];
+                v[3] = values[3];
+                v[4] = values[4];
+                v[5] = values[5];
+            } else {
+                // the pool was empty, we need to dup the array
+                v = values.clone();
+            }
+
+            Message msg = Message.obtain();
+            msg.what = sensor;
+            msg.obj = v;
+            msg.arg1 = accuracy;
+            mHandler.sendMessage(msg);
+        }
+
+        private final Handler mHandler = new Handler(mLooper) {
+            @Override public void handleMessage(Message msg) {
+                if (msg.arg1 >= 0) {
+                    try {
+                        mListener.onAccuracyChanged(msg.what, msg.arg1);
+                    } catch (AbstractMethodError e) {
+                        // old app that doesn't implement this method
+                        // just ignore it.
+                    }
+                }
+                mListener.onSensorChanged(msg.what, (float[])msg.obj);
+                synchronized (this) {
+                    // put back the array into the pool
+                    if (mValuesPool == null) {
+                        mValuesPool = (float[])msg.obj;
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * {@hide}
+     */
+    public SensorManager(Looper mainLooper) {
+        mSensorService = ISensorService.Stub.asInterface(
+                ServiceManager.getService(Context.SENSOR_SERVICE));
+        
+        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 {
+                sWindowManager.watchRotation(this);
+            } catch (RemoteException e) {
+            }
+        }
+
+        mLooper = mainLooper;
+    }
+
+    /** @return available sensors */
+    public int getSensors() {
+        return _sensors_data_get_sensors();
+    }
+
+    /**
+     * Registers a listener for given sensors.
+     *
+     * @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
+     */
+    public boolean registerListener(SensorListener listener, int sensors) {
+        return registerListener(listener, sensors, SENSOR_DELAY_NORMAL);
+    }
+
+    /**
+     * Registers a listener for given sensors.
+     *
+     * @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
+     */
+    public boolean registerListener(SensorListener listener, int sensors, int rate) {
+        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.mListener == listener) {
+                        l = i;
+                        break;
+                    }
+                }
+
+                if (l == null) {
+                    l = new ListenerDelegate(listener, sensors);
+                    result = mSensorService.enableSensor(l, sensors, delay);
+                    if (result) {
+                        sListeners.add(l);
+                        sListeners.notify();
+                    }
+                    if (!sListeners.isEmpty()) {
+                        sSensorThread.startLocked(mSensorService);
+                    }
+                } else {
+                    result = mSensorService.enableSensor(l, sensors, delay);
+                    if (result) {
+                        l.addSensors(sensors);
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in registerListener: ", e);
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Unregisters a listener for the sensors with which it is registered.
+     *
+     * @param listener a SensorListener object
+     * @param sensors a bit masks of the sensors to unregister from
+     */
+    public void unregisterListener(SensorListener listener, int sensors) {
+        try {
+            synchronized (sListeners) {
+                final int size = sListeners.size();
+                for (int i=0 ; i<size ; i++) {
+                    ListenerDelegate l = sListeners.get(i);
+                    if (l.mListener == listener) {
+                        // disable these sensors
+                        mSensorService.enableSensor(l, sensors, SENSOR_DISABLE);
+                        // if we have no more sensors enabled on this listener,
+                        // take it off the list.
+                        if (l.removeSensors(sensors) == 0)
+                            sListeners.remove(i);
+                        break;
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in unregisterListener: ", e);
+        }
+    }
+
+    /**
+     * Unregisters a listener for all sensors.
+     *
+     * @param listener a SensorListener object
+     */
+    public void unregisterListener(SensorListener listener) {
+        unregisterListener(listener, SENSOR_ALL);
+    }
+
+
+    /**
+     * Helper function to convert the specified sensor's data to the windows's 
+     * coordinate space from the device's coordinate space.
+     */ 
+    
+    private static void mapSensorDataToWindow(int sensor, float[] values, int orientation) {
+        final float x = values[DATA_X];
+        final float y = values[DATA_Y];
+        final float z = values[DATA_Z];
+        // copy the raw raw values...
+        values[RAW_DATA_X] = x;
+        values[RAW_DATA_Y] = y;
+        values[RAW_DATA_Z] = z;
+        // TODO: add support for 180 and 270 orientations
+        if (orientation == Surface.ROTATION_90) {
+            switch (sensor) {
+                case SENSOR_ACCELEROMETER:
+                case SENSOR_MAGNETIC_FIELD:
+                    values[DATA_X] =-y;
+                    values[DATA_Y] = x;
+                    values[DATA_Z] = z; 
+                    break;
+                case SENSOR_ORIENTATION:
+                case SENSOR_ORIENTATION_RAW:
+                    values[DATA_X] = x + ((x < 270) ? 90 : -270);
+                    values[DATA_Y] = z;
+                    values[DATA_Z] = y; 
+                    break;
+            }
+        }
+    }
+
+
+    private static native int _sensors_data_open(FileDescriptor fd);
+    private static native int _sensors_data_close();
+    // returns the sensor's status in the top 4 bits of "res".
+    private static native int _sensors_data_poll(float[] values, int sensors);
+    private static native int _sensors_data_get_sensors();
+
+    /** {@hide} */
+    public void onRotationChanged(int rotation) {
+        synchronized(sListeners) {
+            sRotation  = rotation;
+        }
+    }
+    
+    private static int getRotation() {
+        synchronized(sListeners) {
+            return sRotation;
+        }
+    }
+}
+
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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
new file mode 100644
index 0000000..213813a
--- /dev/null
+++ b/core/java/android/net/ConnectivityManager.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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";
+
+    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;
+        }
+    }
+
+    /**
+     * 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..e1d921f
--- /dev/null
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -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 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);
+}
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..ae74e6f
--- /dev/null
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.util.Config;
+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);
+        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);
+
+        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);
+            }
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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 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..f776abf
--- /dev/null
+++ b/core/java/android/net/NetworkInfo.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 State mState;
+    private DetailedState mDetailedState;
+    private String mReason;
+    private String mExtraInfo;
+    private boolean mIsFailover;
+    /**
+     * Indicates whether network connectivity is possible:
+     */
+    private boolean mIsAvailable;
+
+    public NetworkInfo(int type) {
+        if (!ConnectivityManager.isNetworkTypeValid(type)) {
+            throw new IllegalArgumentException("Invalid network type: " + type);
+        }
+        this.mNetworkType = type;
+        setDetailedState(DetailedState.IDLE, null, null);
+        mState = State.UNKNOWN;
+        mIsAvailable = true;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /** {@hide} */
+    public void setFailover(boolean isFailover) {
+        mIsFailover = isFailover;
+    }
+
+    /**
+     * 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.
+     *
+     * {@hide}
+     */
+    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(", state: ").append(mState).
+                append("/").append(mDetailedState).
+                append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
+                append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+                append(", failover: ").append(mIsFailover).
+                append(", isAvailable: ").append(mIsAvailable);
+        return builder.toString();
+    }
+
+    public String getTypeName() {
+        switch (mNetworkType) {
+            case ConnectivityManager.TYPE_WIFI:
+                return "WIFI";
+            case ConnectivityManager.TYPE_MOBILE:
+                return "MOBILE";
+            default:
+                return "<invalid>";
+        }
+    }
+
+    /** 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.writeString(mState.name());
+        dest.writeString(mDetailedState.name());
+        dest.writeInt(mIsFailover ? 1 : 0);
+        dest.writeInt(mIsAvailable ? 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();
+                NetworkInfo netInfo = new NetworkInfo(netType);
+                netInfo.mState = State.valueOf(in.readString());
+                netInfo.mDetailedState = DetailedState.valueOf(in.readString());
+                netInfo.mIsFailover = in.readInt() != 0;
+                netInfo.mIsAvailable = 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..4e1efa6
--- /dev/null
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.PowerManager;
+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 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 NetworkStateTracker(Context context, Handler target, int networkType) {
+        super();
+        mContext = context;
+        mTarget = target;
+        this.mNetworkInfo = new NetworkInfo(networkType);
+    }
+
+    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);
+    }
+
+    /**
+     * 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();
+    }
+
+    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..129248a
--- /dev/null
+++ b/core/java/android/net/NetworkUtils.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+
+    /**
+     * 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..86e1d5b
--- /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.System.getString(
+                contentResolver,
+                Settings.System.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.System.getString(
+                contentResolver,
+                Settings.System.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..f816caa
--- /dev/null
+++ b/core/java/android/net/SSLCertificateSocketFactory.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;
+
+import android.util.Log;
+import android.util.Config;
+import android.net.http.DomainNameChecker;
+import android.os.SystemProperties;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+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 java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.NoSuchAlgorithmException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.GeneralSecurityException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+public class SSLCertificateSocketFactory extends SSLSocketFactory {
+
+    private static final boolean DBG = true;
+    private static final String LOG_TAG = "SSLCertificateSocketFactory";
+    
+    private static X509TrustManager sDefaultTrustManager;
+
+    private final int socketReadTimeoutForSslHandshake;
+
+    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 SSLSocketFactory factory;
+
+    public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
+            throws NoSuchAlgorithmException, KeyManagementException {  
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(null, TRUST_MANAGER, new java.security.SecureRandom());
+        factory = (SSLSocketFactory) context.getSocketFactory();
+        this.socketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
+    }
+
+    /**
+     * Returns a default instantiation of a new socket factory which
+     * only allows SSL connections with valid certificates.
+     *
+     * @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) {
+        try {
+            return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake);
+        } 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) factory.createSocket(s, i, inaddr, j);
+
+        if (socketReadTimeoutForSslHandshake >= 0) {
+            sslSock.setSoTimeout(socketReadTimeoutForSslHandshake);
+        }
+
+        validateSocket(sslSock,s);
+        sslSock.setSoTimeout(0);
+        
+        return sslSock;
+    }
+
+    public Socket createSocket(String s, int i) throws IOException {
+        SSLSocket sslSock = (SSLSocket) factory.createSocket(s, i);
+
+        if (socketReadTimeoutForSslHandshake >= 0) {
+            sslSock.setSoTimeout(socketReadTimeoutForSslHandshake);
+        }
+        
+        validateSocket(sslSock,s);
+        sslSock.setSoTimeout(0);
+
+        return sslSock;
+    }
+
+    public String[] getDefaultCipherSuites() {
+        return factory.getSupportedCipherSuites();
+    }
+
+    public String[] getSupportedCipherSuites() {
+        return factory.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..32a26e4
--- /dev/null
+++ b/core/java/android/net/Uri.java
@@ -0,0 +1,2251 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 encoding and appending a path segment to a base Uri.
+     *
+     * @param baseUri Uri to append path segment to
+     * @param pathSegment to encode and append
+     * @return a new Uri based on baseUri with the given segment encoded and
+     * 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..70e50b7
--- /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 && c <= 255 && ((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..01442ae
--- /dev/null
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.CookieStore;
+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 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.util.concurrent.atomic.AtomicInteger;
+
+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.
+     * @return AndroidHttpClient for you to use for all your requests.
+     */
+    public static AndroidHttpClient newInstance(String userAgent) {
+        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",
+                SSLSocketFactory.getSocketFactory(), 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);
+    }
+
+    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);
+        }
+
+        /**
+         * 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));
+            }
+        }
+    }
+
+    /**
+     * Generates a cURL command equivalent to the given request.
+     */
+    private static String toCurl(HttpUriRequest request) throws IOException {
+        StringBuilder builder = new StringBuilder();
+
+        builder.append("curl ");
+
+        for (Header header: request.getAllHeaders()) {
+            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..b7f7368
--- /dev/null
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+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;
+
+import org.apache.http.HttpHost;
+
+import org.bouncycastle.asn1.x509.X509Name;
+
+/**
+ * Class responsible for all server certificate validation functionality
+ * 
+ * {@hide}
+ */
+class CertificateChainValidator {
+
+    private static long sTotal = 0;
+    private static long sTotalReused = 0;
+
+    /**
+     * 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 SSLHandshakeException, IOException {
+
+        ++sTotal;
+
+        SSLContext sslContext = HttpsConnection.getContext();
+        if (sslContext == null) {
+            closeSocketThrowException(sslSocket, "SSL context is null");
+        }
+
+        X509Certificate[] serverCertificates = null;
+
+        long sessionBeforeHandshakeLastAccessedTime = 0;
+        byte[] sessionBeforeHandshakeId = null;
+
+        SSLSession sessionAfterHandshake = null;
+
+        synchronized(sslContext) {
+            // get SSL session before the handshake
+            SSLSession sessionBeforeHandshake =
+                getSSLSession(sslContext, connection.getHost());
+            if (sessionBeforeHandshake != null) {
+                sessionBeforeHandshakeLastAccessedTime =
+                    sessionBeforeHandshake.getLastAccessedTime();
+
+                sessionBeforeHandshakeId =
+                    sessionBeforeHandshake.getId();
+            }
+
+            // 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]));
+                    }
+                }
+            }
+
+            // get SSL session after the handshake
+            sessionAfterHandshake =
+                getSSLSession(sslContext, connection.getHost());
+        }
+
+        if (sessionBeforeHandshakeLastAccessedTime != 0 &&
+            sessionAfterHandshake != null &&
+            Arrays.equals(
+                sessionBeforeHandshakeId, sessionAfterHandshake.getId()) &&
+            sessionBeforeHandshakeLastAccessedTime <
+            sessionAfterHandshake.getLastAccessedTime()) {
+
+            if (HttpLog.LOGV) {
+                HttpLog.v("SSL session was reused: total reused: "
+                          + sTotalReused
+                          + " out of total of: " + sTotal);
+
+                ++sTotalReused;
+            }
+
+            // no errors!!!
+            return null;
+        }
+
+        // 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 SSLHandshakeException, IOException {
+        closeSocketThrowException(
+            socket, errorMessage != null ? errorMessage : defaultErrorMessage);
+    }
+
+    private void closeSocketThrowException(SSLSocket socket, String errorMessage)
+        throws SSLHandshakeException, 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);
+    }
+
+    /**
+     * @param sslContext The SSL context shared accross all the SSL sessions
+     * @param host The host associated with the session
+     * @return A suitable SSL session from the SSL context
+     */
+    private SSLSession getSSLSession(SSLContext sslContext, HttpHost host) {
+        if (sslContext != null && host != null) {
+            Enumeration en = sslContext.getClientSessionContext().getIds();
+            while (en.hasMoreElements()) {
+                byte[] id = (byte[]) en.nextElement();
+                if (id != null) {
+                    SSLSession session =
+                        sslContext.getClientSessionContext().getSession(id);
+                    if (session.isValid() &&
+                        host.getHostName().equals(session.getPeerHost()) &&
+                        host.getPort() == session.getPeerPort()) {
+                        return session;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+}
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..2c82582
--- /dev/null
+++ b/core/java/android/net/http/Connection.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 (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..5d85ba4
--- /dev/null
+++ b/core/java/android/net/http/Headers.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 string
+    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;
+
+    private long transferEncoding;
+    private long contentLength; // Content length of the incoming data
+    private int connectionType;
+
+    private String contentType;
+    private String contentEncoding;
+    private String location;
+    private String wwwAuthenticate;
+    private String proxyAuthenticate;
+    private String contentDisposition;
+    private String acceptRanges;
+    private String expires;
+    private String cacheControl;
+    private String lastModified;
+    private String etag;
+    private String pragma;
+    private String refresh;
+    private ArrayList<String> cookies = new ArrayList<String>(2);
+
+    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++;
+
+        if (HttpLog.LOGV) {
+            String val = buffer.substringTrimmed(pos, buffer.length());
+            HttpLog.v("hdr " + buffer.length() + " " + buffer);
+        }
+
+        switch (name.hashCode()) {
+        case HASH_TRANSFER_ENCODING:
+            if (name.equals(TRANSFER_ENCODING)) {
+                // headers.transferEncoding =
+                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(buffer
+                        .substringTrimmed(pos, buffer.length()))) {
+                    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)) {
+                try {
+                    contentLength = Long.parseLong(buffer.substringTrimmed(pos,
+                            buffer.length()));
+                } 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)) {
+                contentType = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_CONTENT_ENCODING:
+            if (name.equals(CONTENT_ENCODING)) {
+                contentEncoding = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_CONN_DIRECTIVE:
+            if (name.equals(CONN_DIRECTIVE)) {
+                setConnectionType(buffer, pos);
+            }
+            break;
+        case HASH_LOCATION:
+            if (name.equals(LOCATION)) {
+                location = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_PROXY_CONNECTION:
+            if (name.equals(PROXY_CONNECTION)) {
+                setConnectionType(buffer, pos);
+            }
+            break;
+        case HASH_WWW_AUTHENTICATE:
+            if (name.equals(WWW_AUTHENTICATE)) {
+                wwwAuthenticate = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_PROXY_AUTHENTICATE:
+            if (name.equals(PROXY_AUTHENTICATE)) {
+                proxyAuthenticate = buffer.substringTrimmed(pos, buffer
+                        .length());
+            }
+            break;
+        case HASH_CONTENT_DISPOSITION:
+            if (name.equals(CONTENT_DISPOSITION)) {
+                contentDisposition = buffer.substringTrimmed(pos, buffer
+                        .length());
+            }
+            break;
+        case HASH_ACCEPT_RANGES:
+            if (name.equals(ACCEPT_RANGES)) {
+                acceptRanges = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_EXPIRES:
+            if (name.equals(EXPIRES)) {
+                expires = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_CACHE_CONTROL:
+            if (name.equals(CACHE_CONTROL)) {
+                cacheControl = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_LAST_MODIFIED:
+            if (name.equals(LAST_MODIFIED)) {
+                lastModified = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_ETAG:
+            if (name.equals(ETAG)) {
+                etag = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_SET_COOKIE:
+            if (name.equals(SET_COOKIE)) {
+                cookies.add(buffer.substringTrimmed(pos, buffer.length()));
+            }
+            break;
+        case HASH_PRAGMA:
+            if (name.equals(PRAGMA)) {
+                pragma = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        case HASH_REFRESH:
+            if (name.equals(REFRESH)) {
+                refresh = buffer.substringTrimmed(pos, buffer.length());
+            }
+            break;
+        default:
+            // ignore
+        }
+    }
+
+    public long getTransferEncoding() {
+        return transferEncoding;
+    }
+
+    public long getContentLength() {
+        return contentLength;
+    }
+
+    public int getConnectionType() {
+        return connectionType;
+    }
+
+    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;
+        }
+    }
+
+    public String getContentType() {
+        return this.contentType;
+    }
+
+    public String getContentEncoding() {
+        return this.contentEncoding;
+    }
+
+    public String getLocation() {
+        return this.location;
+    }
+
+    public String getWwwAuthenticate() {
+        return this.wwwAuthenticate;
+    }
+
+    public String getProxyAuthenticate() {
+        return this.proxyAuthenticate;
+    }
+
+    public String getContentDisposition() {
+        return this.contentDisposition;
+    }
+
+    public String getAcceptRanges() {
+        return this.acceptRanges;
+    }
+
+    public String getExpires() {
+        return this.expires;
+    }
+
+    public String getCacheControl() {
+        return this.cacheControl;
+    }
+
+    public String getLastModified() {
+        return this.lastModified;
+    }
+
+    public String getEtag() {
+        return this.etag;
+    }
+
+    public ArrayList<String> getSetCookie() {
+        return this.cookies;
+    }
+    
+    public String getPragma() {
+        return this.pragma;
+    }
+    
+    public String getRefresh() {
+        return this.refresh;
+    }
+
+    public void setContentLength(long value) {
+        this.contentLength = value;
+    }
+
+    public void setContentType(String value) {
+        this.contentType = value;
+    }
+
+    public void setContentEncoding(String value) {
+        this.contentEncoding = value;
+    }
+
+    public void setLocation(String value) {
+        this.location = value;
+    }
+
+    public void setWwwAuthenticate(String value) {
+        this.wwwAuthenticate = value;
+    }
+
+    public void setProxyAuthenticate(String value) {
+        this.proxyAuthenticate = value;
+    }
+
+    public void setContentDisposition(String value) {
+        this.contentDisposition = value;
+    }
+
+    public void setAcceptRanges(String value) {
+        this.acceptRanges = value;
+    }
+
+    public void setExpires(String value) {
+        this.expires = value;
+    }
+
+    public void setCacheControl(String value) {
+        this.cacheControl = value;
+    }
+
+    public void setLastModified(String value) {
+        this.lastModified = value;
+    }
+
+    public void setEtag(String value) {
+        this.etag = value;
+    }
+}
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..bcbecf0
--- /dev/null
+++ b/core/java/android/net/http/Request.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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(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..5d81250
--- /dev/null
+++ b/core/java/android/net/http/RequestHandle.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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";
+
+    private 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;
+    }
+
+    /**
+     * 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)
+     */
+    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";
+        }
+        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..d592995
--- /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.mHost;
+        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/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..bf47555
--- /dev/null
+++ b/core/java/android/os/BatteryManager.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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
+    public static final int BATTERY_PLUGGED_AC = 1;
+    public static final int BATTERY_PLUGGED_USB = 2;
+
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
new file mode 100644
index 0000000..528e6bd
--- /dev/null
+++ b/core/java/android/os/Binder.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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}reference/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();
+            FileOutputStream fout = fd != null
+                    ? new FileOutputStream(fd.getFileDescriptor()) : null;
+            PrintWriter pw = fout != null ? new PrintWriter(fout) : null;
+            if (pw != null) {
+                String[] args = data.readStringArray();
+                dump(fd.getFileDescriptor(), pw, args);
+                pw.flush();
+            }
+            if (fd != null) {
+                try {
+                    fd.close();
+                } catch (IOException e) {
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 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);
+
+    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..cdf907b
--- /dev/null
+++ b/core/java/android/os/Build.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.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");
+
+    /** 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..b9b2773
--- /dev/null
+++ b/core/java/android/os/Debug.java
@@ -0,0 +1,699 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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}reference/traceview.html">Running the Traceview Debugging Program</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 (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 -- this is a temporary measure.
+     *
+     * If a debugger is currently attached the change may not happen
+     * until after the debugger disconnects.
+     */
+    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}reference/traceview.html">Running the Traceview Debugging Program</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}reference/traceview.html">Running the Traceview Debugging Program</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}reference/traceview.html">Running the Traceview Debugging Program</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}reference/traceview.html">Running the Traceview Debugging Program</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();
+    }
+
+    /**
+     * 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..e37b551
--- /dev/null
+++ b/core/java/android/os/Environment.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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_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..b6f38d9
--- /dev/null
+++ b/core/java/android/os/Handler.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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, intent 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";
+
+    /**
+     * 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 {
+            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;
+    }
+
+    /**
+     * Use the provided queue instead of the default one.
+     */
+    public Handler(Looper looper)
+    {
+        mLooper = looper;
+        mQueue = looper.mQueue;
+    }
+
+    /**
+     * 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;
+    IMessenger mMessenger;
+}
diff --git a/core/java/android/os/HandlerInterface.java b/core/java/android/os/HandlerInterface.java
new file mode 100644
index 0000000..62dc273e
--- /dev/null
+++ b/core/java/android/os/HandlerInterface.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;
+
+/** 
+ * @hide 
+ * @deprecated
+ */
+public interface HandlerInterface
+{
+    void handleMessage(Message msg);
+}
+
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..3ec0e9b
--- /dev/null
+++ b/core/java/android/os/IBinder.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;
+
+/**
+ * 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);
+    
+    /**
+     * 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..aa43852
--- /dev/null
+++ b/core/java/android/os/ICheckinService.aidl
@@ -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.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 {
+    /** 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);
+}
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..0397446
--- /dev/null
+++ b/core/java/android/os/IMountService.aidl
@@ -0,0 +1,51 @@
+/* //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);
+}
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/os/INetStatService.aidl
new file mode 100644
index 0000000..fb840d8
--- /dev/null
+++ b/core/java/android/os/INetStatService.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 android.os;
+
+/**
+ * Retrieves packet and byte counts for the phone data interface.
+ * Used for the data activity icon and the phone status in Settings.
+ *
+ * {@hide}
+ */
+interface INetStatService {
+    int getTxPackets();
+    int getRxPackets();
+    int getTxBytes();
+    int getRxBytes();
+}
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..9d05917
--- /dev/null
+++ b/core/java/android/os/IPowerManager.aidl
@@ -0,0 +1,31 @@
+/* //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(boolean val);
+    long getScreenOnTime();
+}
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..80b68e2
--- /dev/null
+++ b/core/java/android/os/Looper.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     */
+    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.
+     */
+    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);
+    }
+
+    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..7312236
--- /dev/null
+++ b/core/java/android/os/NetStat.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 NetStat{
+
+    /**
+     * Get total number of tx packets sent through ppp0
+     *
+     * @return number of Tx packets through ppp0
+     */
+
+    public native static int netStatGetTxPkts();
+
+    /**
+     *  Get total number of rx packets received through ppp0
+     *
+     * @return number of Rx packets through ppp0
+     */
+    public native static int netStatGetRxPkts();
+
+      /**
+     *  Get total number of tx bytes received through ppp0
+     *
+     * @return number of Tx bytes through ppp0
+     */
+    public native static int netStatGetTxBytes();
+
+    /**
+     *  Get total number of rx bytes received through ppp0
+     *
+     * @return number of Rx bytes through ppp0
+     */
+    public native static int netStatGetRxBytes();
+
+}
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..ed138cb
--- /dev/null
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+    
+    /**
+     * 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;
+    }
+    
+    /**
+     * 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..0794e6d
--- /dev/null
+++ b/core/java/android/os/Power.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.os;
+
+/**
+ * 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.
+     */
+    public static native void reboot(String reason);
+}
+
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..2be7768
--- /dev/null
+++ b/core/java/android/os/Process.java
@@ -0,0 +1,694 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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,
+                                  boolean enableDebugger,
+                                  String[] zygoteArgs)
+    {
+        if (supportsProcesses()) {
+            try {
+                return startViaZygote(processClass, niceName, uid, gid, gids,
+                        enableDebugger, 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, boolean enableDebugger, String[] zygoteArgs) {
+        return start(processClass, "", uid, gid, gids, 
+                enableDebugger, 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,
+                                  boolean enableDebugger,
+                                  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 (enableDebugger) {
+                argsForZygote.add("--enable-debugger");
+            }
+
+            //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 UID assigned to a partlicular 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..b6d2999
--- /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}devel/resources-i18n.html">Resources</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/DateFormat.java b/core/java/android/pim/DateFormat.java
new file mode 100644
index 0000000..802e045
--- /dev/null
+++ b/core/java/android/pim/DateFormat.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Context;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.SpannedString;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+    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;Best day evar: &apos;M/d/yy&quot; -&gt; &quot;Best day evar: 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';
+
+    /**
+     * @return true if the user has set the system to use a 24 hour time
+     * format, else false.
+     */
+    public static boolean is24HourFormat(Context context) {
+        String value = Settings.System.getString(context.getContentResolver(),
+                Settings.System.TIME_12_24);
+        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);
+        return new java.text.SimpleDateFormat(b24 ? "H:mm" : "h:mm a");
+    }
+
+    /**
+     * 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 = "MMMM dd, yyyy";
+        } else {
+            value = "dd MMMM, yyyy";
+        }
+        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) {
+            value = "MM-dd-yyyy";
+        }
+        return value;
+    }
+
+    public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) {
+        return format(inFormat, new Date(inTimeInMillis));
+    }
+
+    public static final CharSequence format(CharSequence inFormat, Date inDate) {
+        Calendar    c = new GregorianCalendar();
+        
+        c.setTime(inDate);
+        
+        return format(inFormat, c);
+    }
+
+    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/pim/DateUtils.java b/core/java/android/pim/DateUtils.java
new file mode 100644
index 0000000..2a01f12
--- /dev/null
+++ b/core/java/android/pim/DateUtils.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.pim;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import com.android.internal.R;
+
+/**
+ */
+public class DateUtils
+{
+    private static final String TAG = "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 int sFirstDay;
+    private static Configuration sLastConfig;
+    private static String sStatusDateFormat;
+    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_24HOUR         = 0x00040;
+    public static final int FORMAT_CAP_AMPM       = 0x00080;
+    public static final int FORMAT_NO_NOON        = 0x00100;
+    public static final int FORMAT_CAP_NOON       = 0x00200;
+    public static final int FORMAT_NO_MIDNIGHT    = 0x00400;
+    public static final int FORMAT_CAP_MIDNIGHT   = 0x00800;
+    public static final int FORMAT_UTC            = 0x01000;
+    public static final int FORMAT_ABBREV_TIME    = 0x02000;
+    public static final int FORMAT_ABBREV_WEEKDAY = 0x04000;
+    public static final int FORMAT_ABBREV_MONTH   = 0x08000;
+    public static final int FORMAT_NUMERIC_DATE   = 0x10000;
+    public static final int FORMAT_ABBREV_ALL     = (FORMAT_ABBREV_TIME
+            | FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_MONTH);
+    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 HOUR_MINUTE_AMPM = "%-l:%M%P";
+    public static final String HOUR_MINUTE_CAP_AMPM = "%-l:%M%p";
+    public static final String HOUR_AMPM = "%-l%P";
+    public static final String HOUR_CAP_AMPM = "%-l%p";
+    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 #getDayOfWeekStr} and {@link #getMonthStr}.
+     * @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 #getDayOfWeekStr} and {@link #getMonthStr}.
+     * @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 #getDayOfWeekStr} and {@link #getMonthStr}.
+     * @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 #getDayOfWeekStr} and {@link #getMonthStr}.
+     * @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 #getDayOfWeekStr} and {@link #getMonthStr}.
+     * @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 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.
+     */
+    public static String getAMPMString(int ampm) {
+        Resources r = Resources.getSystem();
+        return r.getString(sAmPm[ampm - Calendar.AM]);
+    }
+
+    /**
+     * Return a 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}.
+     */
+    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]);
+    }
+
+    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) {
+        Resources r = Resources.getSystem();
+
+        // TODO: Assembling strings by hand like this is bad style for i18n.
+        boolean past = (now > time);
+        String prefix = past ? null : r.getString(com.android.internal.R.string.in);
+        String postfix = past ? r.getString(com.android.internal.R.string.ago) : null;
+        return getRelativeTimeSpanString(time, now, minResolution, prefix, postfix);
+    }
+
+    public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution, 
+            String prefix, String postfix) {
+        Resources r = Resources.getSystem(); 
+        
+        long duration = Math.abs(now - time);
+        
+        if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
+            long count = duration / SECOND_IN_MILLIS;
+            String singular = r.getString(com.android.internal.R.string.second);
+            String plural = r.getString(com.android.internal.R.string.seconds);
+            return pluralizedSpan(count, singular, plural, prefix, postfix);
+        }
+
+        if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
+            long count = duration / MINUTE_IN_MILLIS;
+            String singular = r.getString(com.android.internal.R.string.minute);
+            String plural = r.getString(com.android.internal.R.string.minutes);
+            return pluralizedSpan(count, singular, plural, prefix, postfix);
+        }
+
+        if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
+            long count = duration / HOUR_IN_MILLIS;
+            String singular = r.getString(com.android.internal.R.string.hour);
+            String plural = r.getString(com.android.internal.R.string.hours);
+            return pluralizedSpan(count, singular, plural, prefix, postfix);
+        }
+
+        if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
+            return getRelativeDayString(r, time, now);
+        }
+        
+        return dateString(time);
+    }
+    
+
+    private static final String pluralizedSpan(long count, String singular, String plural, 
+            String prefix, String postfix) {
+        StringBuilder s = new StringBuilder();
+
+        if (prefix != null) {
+            s.append(prefix);
+            s.append(" ");
+        }
+        
+        s.append(count);
+        s.append(' ');
+        s.append(count == 0 || count > 1 ? plural : singular);
+        
+        if (postfix != null) {
+            s.append(" ");
+            s.append(postfix);
+        }
+
+        return s.toString();
+    }
+
+    /**
+     * 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);
+        }
+        
+        if (!past) {
+            return r.getString(com.android.internal.R.string.daysDurationFuturePlural, days);
+        } else {
+            return r.getString(com.android.internal.R.string.daysDurationPastPlural, 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);
+                sStatusDateFormat = r.getString(com.android.internal.R.string.status_bar_date_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);
+    }
+    
+    /**
+     * Format a date so it appears like it would in the status bar clock.
+     * @deprecated use {@link #DateFormat.getDateFormat(Context)} instead.
+     * @hide
+     */
+    public static final CharSequence dateString(long startTime) {
+        initFormatStrings();
+        return DateFormat.format(sStatusDateFormat, startTime);
+    }
+
+    /**
+     * Formats an elapsed time like MM:SS or H:MM:SS
+     * for display on the call-in-progress screen.
+     */
+    public static String formatElapsedTime(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(sElapsedFormatHMMSS, hours, minutes, seconds);
+        } else {
+            return formatElapsedTime(sElapsedFormatMMSS, minutes, seconds);
+        }
+    }
+
+    /**
+     * Fast formatting of h:mm:ss
+     */
+    private static String formatElapsedTime(String format, long hours, long minutes, long seconds) {
+        if (FAST_FORMAT_HMMSS.equals(format)) {
+            StringBuffer sb = new StringBuffer(16);
+            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(String format, long minutes, long seconds) {
+        if (FAST_FORMAT_MMSS.equals(format)) {
+            StringBuffer sb = new StringBuffer(16);
+            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.pim.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.pim.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.pim.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.pim.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.pim.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.pim.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.pim.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.pim.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.pim.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.pim.Time}
+     */
+    public static void assign(Calendar lval, Calendar rval)
+    {
+        // there should be a faster way.
+        lval.clear();
+        lval.setTimeInMillis(rval.getTimeInMillis());
+    }
+
+    /**
+     * Creates a string describing a date/time range.  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_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.
+     * 
+     * <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_24HOUR is set and the time is shown, then the time is
+     * shown in the 24-hour time format.
+     * 
+     * <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 FORMAT_24HOUR is not set, 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".
+     * 
+     * <p>
+     * Example output strings:
+     * <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>
+     * </ul>
+     * @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 with the formatted date/time range.
+     */
+    public static String formatDateRange(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) != 0;
+        boolean abbrevMonth = (flags & FORMAT_ABBREV_MONTH) != 0;
+        boolean use24Hour = (flags & FORMAT_24HOUR) != 0;
+        boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
+        boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
+    
+        Time startDate;
+        Time endDate;
+        
+        if (useUTC) {
+            startDate = new Time(Time.TIMEZONE_UTC);
+            endDate = new Time(Time.TIMEZONE_UTC);
+        } else {
+            startDate = new Time();
+            endDate = new Time();
+        }
+        
+        startDate.set(startMillis);
+        endDate.set(endMillis);
+        int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
+        int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
+        int 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 an 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 ((endDate.hour | endDate.minute | endDate.second) == 0
+                && (!showTime || dayDistance <= 1) && (startMillis != endMillis)) {
+            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 = endDate.format(weekDayFormat);
+        }
+        
+        String startTimeString = "";
+        String endTimeString = "";
+        if (showTime) {
+            String startTimeFormat = "";
+            String endTimeFormat = "";
+            if (use24Hour) {
+                startTimeFormat = HOUR_MINUTE_24;
+                endTimeFormat = HOUR_MINUTE_24;
+            } else {
+                boolean abbrevTime = (flags & FORMAT_ABBREV_TIME) != 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 = HOUR_CAP_AMPM;
+                    } else {
+                        startTimeFormat = HOUR_AMPM;
+                    }
+                } else {
+                    if (capAMPM) {
+                        startTimeFormat = HOUR_MINUTE_CAP_AMPM;
+                    } else {
+                        startTimeFormat = HOUR_MINUTE_AMPM;
+                    }
+                }
+                if (abbrevTime && endOnTheHour) {
+                    if (capAMPM) {
+                        endTimeFormat = HOUR_CAP_AMPM;
+                    } else {
+                        endTimeFormat = HOUR_AMPM;
+                    }
+                } else {
+                    if (capAMPM) {
+                        endTimeFormat = HOUR_MINUTE_CAP_AMPM;
+                    } else {
+                        endTimeFormat = HOUR_MINUTE_AMPM;
+                    }
+                }
+                
+                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.
+                }
+                
+                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);
+                    }
+                }
+            }
+            startTimeString = startDate.format(startTimeFormat);
+            endTimeString = endDate.format(endTimeFormat);
+        }
+        
+        // Get the current year
+        long millis = System.currentTimeMillis();
+        Time time = new Time();
+        time.set(millis);
+        int currentYear = time.year;
+    
+        // 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;
+        showYear = showYear || (!noYear && (startYear != endYear || startYear != currentYear));
+        
+        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 = endDate.format(monthFormat);
+        String endMonthDayString = endDate.format(MONTH_DAY_FORMAT);
+        String endYearString = 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 (startMillis == endMillis) {
+                // 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;
+    }
+
+    /**
+     * @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:
+     * "millis=0:10 today" will be displayed as "0:10" 
+     * "millis=11:30pm the day before" will be displayed as "Mar 30"
+     * A similar scheme is used to dates that are a week, a month or more than a year old. 
+     * 
+     * @param withPreposition If true, the string returned will include the correct 
+     * preposition ("at 9:20am", "in 2008" or "on May 29").
+     */
+    public static CharSequence getRelativeTimeSpanString(Context c, long millis,
+            boolean withPreposition) {
+
+        long span = System.currentTimeMillis() - millis;
+
+        Resources res = c.getResources();
+        if (sNowTime == null) {
+            sNowTime = new Time();
+            sThenTime = new Time();
+            sMonthDayFormat = res.getString(com.android.internal.R.string.abbrev_month_day);
+        }
+
+        sNowTime.setToNow();
+        sThenTime.set(millis);
+
+        if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) {
+            // Same day
+            return getPrepositionDate(res, sThenTime, R.string.preposition_for_time,
+                    HOUR_MINUTE_CAP_AMPM, withPreposition);
+        } else if (sNowTime.year != sThenTime.year) {
+            // Different years
+            // TODO: take locale into account so that the display will adjust correctly.
+            return getPrepositionDate(res, sThenTime, R.string.preposition_for_year,
+                    NUMERIC_MONTH_FORMAT + "/" + MONTH_DAY_FORMAT + "/" + YEAR_FORMAT_TWO_DIGITS,
+                    withPreposition);
+        } else {
+            // Default
+            return getPrepositionDate(res, sThenTime, R.string.preposition_for_date,
+                sMonthDayFormat, withPreposition);
+        }
+    }
+    
+    /**
+     * @return A date string suitable for display based on the format and including the
+     * date preposition if withPreposition is true.
+     */
+    private static String getPrepositionDate(Resources res, Time thenTime, int id,
+            String formatString, boolean withPreposition) {
+        String result = thenTime.format(formatString);
+        return withPreposition ? res.getString(id, result) : result;
+    }
+    
+    public static CharSequence getRelativeTimeSpanString(Context c, long millis) {
+        return getRelativeTimeSpanString(c, millis, false /* no preposition */);
+    }
+    
+    private static Time sNowTime;
+    private static Time sThenTime;
+    private static String sMonthDayFormat;
+}
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
new file mode 100644
index 0000000..ad671f68
--- /dev/null
+++ b/core/java/android/pim/EventRecurrence.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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..4a5d7e4
--- /dev/null
+++ b/core/java/android/pim/ICalendar.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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) {
+        // first we deal with line folding, by replacing all "\r\n " strings
+        // with nothing
+        text = text.replaceAll("\r\n ", "");
+
+        // it's supposed to be \r\n, but not everyone does that
+        text = text.replaceAll("\r\n", "\n");
+        text = text.replaceAll("\r", "\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;
+        char c = line.charAt(state.index);
+        if (c != ':') {
+            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..c02ff52
--- /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.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);
+        boolean rdateNotInUtc = !tz.equals(Time.TIMEZONE_UTC);
+        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.parse2445(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.parse2445(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.parse2445(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/Time.java b/core/java/android/pim/Time.java
new file mode 100644
index 0000000..59ba87b
--- /dev/null
+++ b/core/java/android/pim/Time.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 java.util.TimeZone;
+
+/**
+ * {@hide}
+ *
+ * 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 {
+    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>
+     */
+    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;
+
+    /**
+     * Construct a Time object in the timezone named by the string
+     * argument "timezone". The time is initialized to Jan 1, 1970.
+     */
+    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 local 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.
+     */
+    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".
+     */
+    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.
+     */
+    native public String format(String format);
+
+    /**
+     * Return the current time in YYYYMMDDTHHMMSS<tz> format
+     */
+    @Override
+    native public String toString();
+
+    /**
+     * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
+     */
+    native public void parse(String s);
+
+    /**
+     * Parse a time in RFC 2445 format. Returns whether or not the time is in
+     * UTC (ends with Z).
+     *
+     * @param s the string to parse
+     * @return true if the resulting time value is in UTC time
+     */
+    public boolean parse2445(String s) {
+        if (nativeParse2445(s)) {
+            timezone = TIMEZONE_UTC;
+            return true;
+        }
+        return false;
+    }
+
+    native private boolean nativeParse2445(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).  If the string
+     * contains a time and time offset, then the time offset will be used to
+     * convert the time value to UTC.
+     * Returns true if the resulting time value is in UTC time.
+     *
+     * @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;
+    }
+
+    /**
+     * Set the fields. Sets weekDay, yearDay and gmtoff to 0. Call
+     * normalize() 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;
+    }
+
+    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;
+    }
+
+    public boolean before(Time that) {
+        return Time.compare(this, that) < 0;
+    }
+
+    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.
+     * 
+     * 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.
+     * 
+     * The week number can be calculated by counting Thursdays.  Week N
+     * contains the Nth Thursday of the year.
+     *   
+     * @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;
+    }
+
+    public String format3339(boolean allDay) {
+        if (allDay) {
+            return format("%Y-%m-%d");
+        } else if (TIMEZONE_UTC.equals(timezone)) {
+            return format("%Y-%m-%dT%H:%M:%S.000Z");
+        } else {
+            String base = format("%Y-%m-%dT%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);
+        }
+    }
+    
+    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>
+     * 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.
+     * 
+     * @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/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..4bebf87
--- /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;
+
+/**
+ * The {@link CheckBoxPreference} is a 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..3eb65e2
--- /dev/null
+++ b/core/java/android/preference/DialogPreference.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * The {@link DialogPreference} class is a base class for preferences 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.BUTTON1);
+    }
+
+    /**
+     * 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) {
+    }
+
+    /**
+     * {@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..be56003
--- /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;
+
+/**
+ * The {@link EditTextPreference} class is a 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..6c98ded
--- /dev/null
+++ b/core/java/android/preference/ListPreference.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * The {@link ListPreference} is a 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;
+                    }
+        });
+    }
+
+    @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..1db7525
--- /dev/null
+++ b/core/java/android/preference/Preference.java
@@ -0,0 +1,1577 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * The {@link Preference} class represents the basic preference UI building
+ * block that is 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 tag name 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 this
+     * {@link Preference Preference's} value 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 this preference has been changed by the user. This is
+         * called before the preference's state is about to be updated and
+         * before the state is persisted.
+         * 
+         * @param preference This preference.
+         * @param newValue The new value of the preference.
+         * @return Whether or not to update this preference's state with the new value.
+         */
+        boolean onPreferenceChange(Preference preference, Object newValue);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a preference is
+     * clicked.
+     */
+    public interface OnPreferenceClickListener {
+        /**
+         * Called when a preference has been clicked.
+         *
+         * @param preference The preference that was clicked.
+         * @return Whether 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 {@link Preference} allows subclasses to use their own base
+     * style when they are inflating. For example, a {@link CheckBoxPreference}'s
+     * constructor would call this version of the super class constructor and
+     * supply {@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 to store preference values.
+     */
+    public Preference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Called when {@link 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 the preference is clicked.
+     * 
+     * @param intent The intent associated with the 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 preferences 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 a preference. This
+     * is inflated into the main layout. For example, a checkbox preference
+     * 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 a 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 the 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, mEnabled);
+        }
+    }
+    
+    /**
+     * 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 {@link Preference} with respect to other
+     * {@link Preference} 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
+     * preferences 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 {@link Preference}.
+     * 
+     * @return The order of this {@link Preference}.
+     * @see #setOrder(int)
+     */
+    public int getOrder() {
+        return mOrder;
+    }
+
+    /**
+     * Sets the title for the preference. 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 of the preference.
+     */
+    public void setTitle(CharSequence title) {
+        if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
+            mTitle = title;
+            notifyChanged();
+        }
+    }
+    
+    /**
+     * @see #setTitle(CharSequence)
+     * @param titleResId The title as a resource ID.
+     */
+    public void setTitle(int titleResId) {
+        setTitle(mContext.getString(titleResId));
+    }
+    
+    /**
+     * Returns the title of the preference.
+     * 
+     * @return The title.
+     * @see #setTitle(CharSequence)
+     */
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns the summary of the preference.
+     * 
+     * @return The summary.
+     * @see #setSummary(CharSequence)
+     */
+    public CharSequence getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * Sets the summary for the preference. This summary will be placed into the
+     * ID {@link android.R.id#summary} within the View created by
+     * {@link #onCreateView(ViewGroup)}.
+     * 
+     * @param summary The summary of the preference.
+     */
+    public void setSummary(CharSequence summary) {
+        if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) {
+            mSummary = summary;
+            notifyChanged();
+        }
+    }
+
+    /**
+     * @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, the preference will
+     * not handle clicks.
+     * 
+     * @param enabled Whether the preference is enabled.
+     */
+    public void setEnabled(boolean enabled) {
+        if (mEnabled != enabled) {
+            mEnabled = enabled;
+
+            // Enabled state can change dependent preferences' states, so notify
+            notifyDependencyChange(shouldDisableDependents());
+
+            notifyChanged();
+        }
+    }
+    
+    /**
+     * Whether this {@link Preference} should be enabled in the list.
+     * 
+     * @return Whether this preference is enabled.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Sets whether this preference is selectable.
+     * 
+     * @param selectable Whether the preference is selectable.
+     */
+    public void setSelectable(boolean selectable) {
+        if (mSelectable != selectable) {
+            mSelectable = selectable;
+            notifyChanged();
+        }
+    }
+    
+    /**
+     * Whether this {@link Preference} should be selectable in the list.
+     * 
+     * @return Whether this preference is selectable.
+     */
+    public boolean isSelectable() {
+        return mSelectable;
+    }
+
+    /**
+     * Sets whether this {@link 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 Whether this preference should disable its view
+     *            when the preference is disabled.
+     */
+    public void setShouldDisableView(boolean shouldDisableView) {
+        mShouldDisableView = shouldDisableView;
+        notifyChanged();
+    }
+    
+    /**
+     * @see #setShouldDisableView(boolean)
+     * @return Whether this preference should disable its view when it is disabled. 
+     */
+    public boolean getShouldDisableView() {
+        return mShouldDisableView;
+    }
+
+    /**
+     * Returns a unique ID for this preference.  This ID should be unique across all
+     * preferences 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 the 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.
+     * @see #getId()
+     */
+    public void setKey(String key) {
+        mKey = key;
+        
+        if (mRequiresKey && !hasKey()) {
+            requireKey();
+        }
+    }
+    
+    /**
+     * Gets the key for the 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}.
+     */
+    void requireKey() {
+        if (mKey == null) {
+            throw new IllegalStateException("Preference does not have a key assigned.");
+        }
+        
+        mRequiresKey = true;
+    }
+    
+    /**
+     * Returns whether this {@link Preference} has a valid key.
+     * 
+     * @return Whether the key exists and is not a blank string.
+     */
+    public boolean hasKey() {
+        return !TextUtils.isEmpty(mKey);
+    }
+    
+    /**
+     * Returns whether this {@link Preference} is persistent. If it is persistent, it stores its value(s) into
+     * the persistent {@link SharedPreferences} storage.
+     * 
+     * @return Whether this is persistent.
+     */
+    public boolean isPersistent() {
+        return mPersistent;
+    }
+    
+    /**
+     * Convenience method of whether at the given time this method is called,
+     * the {@link Preference} should store/restore its value(s) into the
+     * {@link SharedPreferences}. This, at minimum, checks whether the
+     * {@link Preference} is persistent and it currently has a key. Before you
+     * save/restore from the {@link SharedPreferences}, check this first.
+     * 
+     * @return Whether to persist the value.
+     */
+    protected boolean shouldPersist() {
+        return mPreferenceManager != null && isPersistent() && hasKey();
+    }
+    
+    /**
+     * Sets whether this {@link Preference} is persistent. If it is persistent,
+     * it stores its value(s) into the persistent {@link SharedPreferences}
+     * storage.
+     * 
+     * @param persistent Whether 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 the preference.
+     * @return Whether or not 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;
+    }
+
+    /**
+     * Gets 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;
+    }
+
+    /**
+     * Gets 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 Optional {@link PreferenceScreen} whose hierarchy click
+     *            listener should be called in the proper order (between other
+     *            processing).
+     */
+    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 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 valus.
+     * 
+     * @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 shared preferences, 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 preferences based on order (if set), otherwise alphabetically on title.
+     * <p>
+     * {@inheritDoc}
+     */
+    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 this is a group a {@link 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'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 preference manager 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
+     * {@link 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);
+            }
+        }
+    }
+    
+    /**
+     * Find 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 {@link Preference} to find.
+     * @return The {@link Preference} object of a preference
+     *         with 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 {@link 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 Whether to disable this preference.
+     */
+    public void onDependencyChanged(Preference dependency, boolean disableDependent) {
+        setEnabled(!disableDependent);
+    }
+    
+    /**
+     * Should return 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 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.
+     * 
+     * @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. If the
+     * restoreValue flag is true, you should restore the value from the shared
+     * preferences. If false, you should set (and possibly store to shared
+     * preferences if {@link #shouldPersist()}) to defaultValue.
+     * <p>
+     * This may not always be called. One example is if it should not persist
+     * but there is no default value given.
+     * 
+     * @param restorePersistedValue Whether to restore the persisted value
+     *            (true), or use the given default value (false).
+     * @param defaultValue The default value. Only use if restoreValue 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 SharedPreferences.
+     * <p>
+     * This will check if the Preference is persistent, get an editor from
+     * the preference manager, put the string, check if we should commit (and
+     * commit if so).
+     * 
+     * @param value The value to persist.
+     * @return Whether 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 SharedPreferences.
+     * <p>
+     * This will check if the Preference is persistent, get the shared
+     * preferences from the preference manager, 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 shared preferences 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 SharedPreferences.
+     * 
+     * @param value The value to persist.
+     * @return Whether 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 SharedPreferences.
+     * 
+     * @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 shared preferences 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 SharedPreferences.
+     * 
+     * @param value The value to persist.
+     * @return Whether 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 #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 SharedPreferences.
+     * 
+     * @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 shared preferences 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 SharedPreferences.
+     * 
+     * @param value The value to persist.
+     * @return Whether 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 #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 SharedPreferences.
+     * 
+     * @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 shared preferences 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 SharedPreferences.
+     * 
+     * @param value The value to persist.
+     * @return Whether 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 #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 SharedPreferences.
+     * 
+     * @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 shared preferences 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 preference's icicles.
+     * 
+     * @see #restoreHierarchyState
+     * @see #dispatchSaveInstanceState
+     * @see #onSaveInstanceState
+     */
+    public void saveHierarchyState(Bundle container) {
+        dispatchSaveInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #saveHierarchyState} to store the icicles for this preference and its children.
+     * May be overridden to modify how freezing happens to a preference's children; for example, some
+     * preferences may want to not store icicles for their children.
+     * 
+     * @param container The Bundle in which to save the preference's icicles.
+     * 
+     * @see #dispatchRestoreInstanceState
+     * @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 Returns a Parcelable object containing the preference's current
+     *         dynamic state, or null if there is nothing interesting to save.
+     *         The default implementation returns null.
+     * @see #onRestoreInstanceState
+     * @see #saveHierarchyState
+     * @see #dispatchSaveInstanceState
+     */
+    protected Parcelable onSaveInstanceState() {
+        mBaseMethodCalled = true;
+        return BaseSavedState.EMPTY_STATE;
+    }
+
+    /**
+     * Restore this preference hierarchy's frozen state from the given container.
+     * 
+     * @param container The Bundle which holds previously frozen icicles.
+     * 
+     * @see #saveHierarchyState
+     * @see #dispatchRestoreInstanceState
+     * @see #onRestoreInstanceState
+     */
+    public void restoreHierarchyState(Bundle container) {
+        dispatchRestoreInstanceState(container);
+    }
+
+    /**
+     * Called by {@link #restoreHierarchyState} to retrieve the icicles for this
+     * preference and its children. May be overridden to modify how restoreing
+     * happens to a preference's children; for example, some preferences may
+     * want to not store icicles for their children.
+     * 
+     * @param container The Bundle which holds previously frozen icicles.
+     * @see #dispatchSaveInstanceState
+     * @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 icicle.
+     * 
+     * @param state The frozen state that had previously been returned by
+     *            {@link #onSaveInstanceState}.
+     * @see #onSaveInstanceState
+     * @see #restoreHierarchyState
+     * @see #dispatchRestoreInstanceState
+     */
+    protected void onRestoreInstanceState(Parcelable state) {
+        mBaseMethodCalled = true;
+        if (state != BaseSavedState.EMPTY_STATE && state != null) {
+            throw new IllegalArgumentException("Wrong state class -- expecting Preference State");
+        }
+    }
+
+    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..98144ca
--- /dev/null
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * The {@link PreferenceActivity} activity shows a hierarchy of preferences 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);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+    
+        setContentView(com.android.internal.R.layout.preference_list_content);
+        
+        mPreferenceManager = onCreatePreferenceManager();
+        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
+    }
+
+    @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();
+        }
+    }
+    
+    /**
+     * 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..a1b6f09
--- /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;
+
+/**
+ * The {@link PreferenceCategory} class is used to group {@link Preference}s
+ * 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..55b3753
--- /dev/null
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -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.
+ */
+
+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.util.AttributeSet;
+
+/**
+ * The {@link PreferenceGroup} class is a container for multiple
+ * {@link Preference}s. It is a base class for {@link Preference} 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) {
+        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..e2a3157
--- /dev/null
+++ b/core/java/android/preference/PreferenceGroupAdapter.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.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;
+        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);
+                }
+            } else {
+                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 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..9963544
--- /dev/null
+++ b/core/java/android/preference/PreferenceManager.java
@@ -0,0 +1,790 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * The {@link PreferenceManager} is used to help create 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;
+        
+        synchronized (this) {
+            if (mActivityDestroyListeners == null) return;
+            list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
+        }
+
+        final int N = list.size();
+        for (int i = 0; i < N; i++) {
+            list.get(i).onActivityDestroy();
+        }
+    }
+    
+    /**
+     * 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) {
+
+        // 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..e4ecb88
--- /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;
+
+/**
+ * The {@link PreferenceScreen} class represents a top-level {@link Preference} that
+ * is the root of a {@link 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)}.
+ * <p>
+ * 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.
+ * <p>
+ * <code>
+ &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;
+ * </code>
+ * 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}.
+     * @see PreferenceGroupAdapter
+     */
+    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..97674ce
--- /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;
+
+/**
+ * The {@link RingtonePreference} allows the user to choose one from all of the
+ * available ringtones. 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..5a0a089
--- /dev/null
+++ b/core/java/android/preference/VolumePreference.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ContentResolver;
+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 OnSeekBarChangeListener,
+        Runnable, PreferenceManager.OnActivityStopListener {
+
+    private static final String TAG = "VolumePreference";
+    
+    private ContentResolver mContentResolver;
+    private Handler mHandler = new Handler();
+
+    private AudioManager mVolume;
+    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(mContentResolver,
+                        System.VOLUME_SETTINGS[mStreamType], 0));
+            }
+        }
+    };
+    
+    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();        
+    
+        mContentResolver = context.getContentResolver();
+        
+        mVolume = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    }
+    
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+    
+        final SeekBar seekBar = mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+        seekBar.setMax(mVolume.getStreamMaxVolume(mStreamType));
+        mOriginalStreamVolume = mVolume.getStreamVolume(mStreamType);
+        seekBar.setProgress(mOriginalStreamVolume);
+        seekBar.setOnSeekBarChangeListener(this);
+        
+        mContentResolver.registerContentObserver(System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver);
+
+        getPreferenceManager().registerOnActivityStopListener(this);
+        mRingtone = RingtoneManager.getRingtone(getContext(), Settings.System.DEFAULT_RINGTONE_URI);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (!positiveResult) {
+            mVolume.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
+        }
+        
+        cleanup();
+    }
+    
+
+    public void onActivityStop() {
+        cleanup();
+    }
+
+    /**
+     * Do clean up.  This can be called multiple times!
+     */
+    private void cleanup() {
+        stopSample();
+        if (mVolumeObserver != null) {
+            mContentResolver.unregisterContentObserver(mVolumeObserver);
+        }
+        getPreferenceManager().unregisterOnActivityStopListener(this);
+        mSeekBar = null;
+    }
+
+    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() {
+        mVolume.setStreamVolume(mStreamType, mLastProgress, 0);
+    }
+    
+    private void sample() {
+        mRingtone.play();
+    }
+
+    private void stopSample() {
+        if (mRingtone != null) {
+            mRingtone.stop();
+        }
+    }
+    
+}
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..7aaed49
--- /dev/null
+++ b/core/java/android/provider/Browser.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.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);
+            /* We should only get one answer that is exactly the same. */
+            if (c.moveToFirst()) {
+                ContentValues map = new ContentValues();
+                map.put(BookmarkColumns.DATE, now);
+                cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null);
+            } else {
+                ContentValues map = new ContentValues();
+                map.put(SearchColumns.SEARCH, search);
+                map.put(SearchColumns.DATE, now);
+                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..b07f1b8
--- /dev/null
+++ b/core/java/android/provider/Calendar.java
@@ -0,0 +1,1115 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.client.AuthenticationException;
+import com.google.wireless.gdata.client.AllDeletedUnavailableException;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.android.internal.database.ArrayListCursor;
+
+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.DateUtils;
+import android.pim.ICalendar;
+import android.pim.RecurrenceSet;
+import android.pim.Time;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.IOException;
+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 original event this event is an exception for
+         * <P>Type: TEXT</P>
+         */
+        public static final String ORIGINAL_EVENT = "originalEvent";
+
+        /**
+         * The time of the original instance time this event is an exception for
+         * <P>Type: INTEGER (long; millis since epoch)</P>
+         */
+        public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
+
+        /**
+         * 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.parse2445(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.parse2445(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 state of this alert.  It starts out as SCHEDULED, then when
+         * the alarm goes off, it changes to FIRED, and then when the user
+         * sees and 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);
+            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..5b79482
--- /dev/null
+++ b/core/java/android/provider/Checkin.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.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 {@link android.server.checkin.CheckinProvider}.
+ * Describes the exposed database schema, and offers methods to add
+ * events and statistics to be uploaded.
+ *
+ * @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 {
+            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,
+            SYSTEM_APP_NOT_RESPONDING,
+            SYSTEM_BOOT,
+            SYSTEM_LAST_KMSG,
+            SYSTEM_RECOVERY_LOG,
+            SYSTEM_RESTART,
+            SYSTEM_SERVICE_LOOPING,
+            SYSTEM_TOMBSTONE,
+            TEST,
+            NETWORK_RX_MOBILE,
+            NETWORK_TX_MOBILE,
+        }
+    }
+
+    /**
+     * 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 {
+            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";
+    }
+
+    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 (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 (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..91b1853
--- /dev/null
+++ b/core/java/android/provider/Contacts.java
@@ -0,0 +1,1606 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 com.android.internal.R;
+
+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 persons name.
+         * <P>Type: TEXT</P>
+         */
+        public static final String NAME = "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 category defined in
+         * {@link android.provider.Im.ProviderCategories} from the predefined IM protocol id.
+         * This is used for interacting with the IM application.
+         * 
+         * @param protocol the protocol ID
+         * @return the provider category the IM app uses for the given protocol, or null if no
+         * provider is defined for the given protocol
+         * @hide
+         */
+        public static String lookupProviderCategoryFromId(int protocol) {
+            switch (protocol) {
+                case PROTOCOL_GOOGLE_TALK:
+                    return Im.ProviderCategories.GTALK;
+                case PROTOCOL_AIM:
+                    return Im.ProviderCategories.AIM;
+                case PROTOCOL_MSN:
+                    return Im.ProviderCategories.MSN;
+                case PROTOCOL_YAHOO:
+                    return Im.ProviderCategories.YAHOO;
+                case PROTOCOL_ICQ:
+                    return Im.ProviderCategories.ICQ;
+            }
+            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[People.Phones.TYPE_HOME - 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 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 type and 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 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 type and 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 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 type and 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..42e9d95a
--- /dev/null
+++ b/core/java/android/provider/Downloads.java
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 the HTTP method to use for this
+     * download. See the METHOD_* constants for a list of legal values.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Init/Read</P>
+     */
+    public static final String METHOD = "method";
+
+    /**
+     * The name of the column containing the entity to be sent with the
+     * request of this download. Only use for methods that support sending
+     * entities, i.e. POST.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init</P>
+     */
+    public static final String ENTITY = "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/Read</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/Read</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>
+     * <P>UI can Read</P>
+     */
+    public static final String FILENAME = "_data";
+
+    /**
+     * The name of the column containing the MIME type of the downloaded data.
+     * <P>Type: TEXT</P>
+     * <P>Owner can Init/Read</P>
+     * <P>UI can 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/Read</P>
+     * <P>UI can Read</P>
+     */
+    public static final String DESTINATION = "destination";
+
+    /**
+     * The name of the column containing the flags that controls whether
+     * the download must be saved with the filename used for OTA updates.
+     * Must be used with INTERNAL, and the initiating application must hold the
+     * android.permission.DOWNLOAD_OTA_UPDATE permission.
+     * <P>Type: BOOLEAN</P>
+     * <P>Owner can Init/Read</P>
+     * <P>UI can Read</P>
+     */
+    public static final String OTA_UPDATE = "otaupdate";
+
+    /**
+     * The name of the columns containing the flag that controls whether
+     * files with private/inernal/system MIME types can be downloaded.
+     * <P>Type: BOOLEAN</P>
+     * <P>Owner can Init/Read</P>
+     */
+    public static final String NO_SYSTEM_FILES = "no_system";
+
+    /**
+     * 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>
+     * <P>UI can Read/Write (only for entries that are visible)</P>
+     */
+    public static final String VISIBILITY = "visibility";
+
+    /**
+     * The name of the column containing the command associated with the
+     * download. After a download is initiated, this is the only column that
+     * applications can modify. See the CONTROL_* constants for a list of legal
+     * values. Note: doesn't do anything in 1.0. The API will be hooked up
+     * in a future version, and is provided here as an indication of things
+     * to come.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Init/Read/Write</P>
+     * <P>UI can Init/Read/Write</P>
+     * @hide
+     */
+    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>
+     * <P>UI 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>
+     * <P>UI can Read</P>
+     */
+    public static final String LAST_MODIFICATION = "lastmod";
+
+    /**
+     * The name of the column containing the number of consecutive connections
+     * that have failed.
+     * <P>Type: INTEGER</P>
+     */
+    public static final String FAILED_CONNECTIONS = "numfailed";
+
+    /**
+     * 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>
+     * <P>UI can 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>
+     * <P>UI can 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.
+     */
+    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>
+     * <P>UI 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>
+     * <P>UI can Read</P>
+     */
+    public static final String CURRENT_BYTES = "current_bytes";
+
+    /**
+     * The name of the column containing the entity tag for the response.
+     * <P>Type: TEXT</P>
+     * @hide
+     */
+    public static final String ETAG = "etag";
+
+    /**
+     * The name of the column containing the UID of the application that
+     * initiated the download.
+     * <P>Type: INTEGER</P>
+     * @hide
+     */
+    public static final String UID = "uid";
+
+    /**
+     * 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.
+     * <P>Type: INTEGER</P>
+     * <P>Owner can Init/Read/Write</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>
+     * <P>UI can Read</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>
+     * <P>UI can Read</P>
+     */
+    public static final String DESCRIPTION = "description";
+
+    /**
+     * The name of the column where the download manager indicates whether the
+     * media scanner was notified about this download.
+     * <P>Type: BOOLEAN</P>
+     * @hide
+     */
+    public static final String MEDIA_SCANNED = "scanned";
+
+    /*
+     * 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).
+     */
+    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 cache
+     * on the shared data partition. Use CACHE_PARTITION_PURGEABLE instead.
+     */
+    public static final int DESTINATION_DATA_CACHE = 3;
+
+    /* (not javadoc)
+     * This download will be saved to a file specified by the initiating
+     * applications.
+     * @hide
+     */
+    //public static final int DESTINATION_PROVIDER = 4;
+
+    /*
+     * Lists the commands that an application can set to control an ongoing
+     * download. Note: those aren't working.
+     */
+
+    /**
+     * This download can run
+     * @hide
+     */
+    public static final int CONTROL_RUN = 0;
+
+    /**
+     * This download must pause (might be restarted)
+     * @hide
+     */
+    public static final int CONTROL_PAUSE = 1;
+
+    /**
+     * This download must abort (will never be restarted)
+     * @hide
+     */
+    public static final int CONTROL_STOP = 2;
+
+    /*
+     * 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;
+
+    /**
+     * The server returned an auth error.
+     */
+    public static final int STATUS_NOT_AUTHORIZED = 401;
+
+    /**
+     * 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;
+    /**
+     * @hide
+     * Alternate spelling
+     */
+    public static final int STATUS_CANCELLED = 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;
+    /**
+     * @hide
+     * Legacy name - use STATUS_UNKNOWN_ERROR
+     */
+    public static final int STATUS_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 code.
+     */
+    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;
+
+    /*
+     * Lists the HTTP methods that the download manager can use.
+     */
+
+    /**
+     * GET
+     */
+    public static final int METHOD_GET = 0;
+
+    /**
+     * POST
+     */
+    public static final int METHOD_POST = 1;
+
+    /**
+     * This download is visible but only shows in the notifications
+     * while it's running (a separate download UI would still show it
+     * after completion).
+     */
+    public static final int VISIBILITY_VISIBLE = 0;
+
+    /**
+     * This download is visible and shows in the notifications 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..038ba21
--- /dev/null
+++ b/core/java/android/provider/Gmail.java
@@ -0,0 +1,2355 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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_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 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);
+
+    /**
+     * 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";
+
+        // 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 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;
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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
+     */
+    public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
+            String query) {
+        if (TextUtils.isEmpty(account)) {
+            throw new IllegalArgumentException("account is empty");
+        }
+        handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
+                CONVERSATION_PROJECTION, query, null, 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
+     */
+    public ConversationCursor getConversationCursorForQuery(String account, String query) {
+        Cursor cursor = mContentResolver.query(
+                Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
+                query, null, 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 maxMessageId the highest message id 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
+     * @throws NonexistentLabelException thrown if the label does not exist
+     */
+    public void addOrRemoveLabelOnConversation(
+            String account, long conversationId, long maxMessageId, String label,
+            boolean add)
+            throws NonexistentLabelException {
+        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, maxMessageId);
+            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[]{"" + maxMessageId});
+        }
+    }
+
+    /**
+     * 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
+     * @throws NonexistentLabelException thrown if the label does not exist
+     */
+    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;
+        String numMessagesFragment = "";
+        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)) {
+                numMessagesFragment = " (" + fragments[i++] + ")";
+            } else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
+                String numDraftsString = fragments[i++];
+                int 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);
+            }
+        }
+
+        // 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);
+    }
+
+    /**
+     * Thrown when an operation is requested with a label that does not exist.
+     *
+     * TODO: this is here because I wanted a checked exception. However, I don't
+     * think that that is appropriate. In fact, I don't think that we should
+     * throw an exception at all because the label might have been valid when
+     * the caller presented it to the user but removed as a result of a sync.
+     * Maybe we should kill this and eat the errors.
+     */
+    public static class NonexistentLabelException extends Exception {
+        // TODO: Add label name?
+    }
+
+    /**
+     * 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 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_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
+                    && 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 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);
+
+        public static List<String> getSortedUserMeaningfulSystemLabels() {
+            return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
+        }
+
+        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
+         * @throws NonexistentLabelException thrown if the named label does not
+         *         exist
+         */
+        public void addOrRemoveLabel(String label, boolean add) throws NonexistentLabelException {
+            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);
+        }
+
+        /**
+         * Gets the 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 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 getMaxMessageId() {
+            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..8ca97e1
--- /dev/null
+++ b/core/java/android/provider/Im.java
@@ -0,0 +1,1937 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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
+         */
+        String NAME = "name";
+
+        /**
+         * The full name of the provider
+         */
+        String FULLNAME = "fullname";
+
+        /**
+         * The url users should visit to create a new account for this provider
+         */
+        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";
+    }
+
+    /**
+     * The ProviderCategories definitions are used for the Intent category for the Intent
+     *
+     *     Intent intent = new Intent(Intent.ACTION_SENDTO,
+     *             Uri.fromParts("im", data, null)).
+     *                     addCategory(category);
+     */
+    public interface ProviderCategories {
+        String GTALK = "com.android.im.category.GTALK";
+        String AIM = "com.android.im.category.AIM";
+        String MSN = "com.android.im.category.MSN";
+        String YAHOO = "com.android.im.category.YAHOO";
+        String ICQ = "com.android.im.category.ICQ";
+    }
+
+    /**
+     * 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;
+        }
+
+        /**
+         * This returns the provider name given a provider category.
+         *
+         * @param providerCategory the provider category defined in {@link ProviderCategories}.
+         * @return the corresponding provider name defined in {@link ProviderNames}.
+         */
+        public static String getProviderNameForCategory(String providerCategory) {
+            if (providerCategory != null) {
+                if (providerCategory.equalsIgnoreCase(ProviderCategories.GTALK)) {
+                    return ProviderNames.GTALK;
+                } else if (providerCategory.equalsIgnoreCase(ProviderCategories.AIM)) {
+                    return ProviderNames.AIM;
+                } else if (providerCategory.equalsIgnoreCase(ProviderCategories.MSN)) {
+                    return ProviderNames.MSN;
+                } else if (providerCategory.equalsIgnoreCase(ProviderCategories.YAHOO)) {
+                    return ProviderNames.YAHOO;
+                } else if (providerCategory.equalsIgnoreCase(ProviderCategories.ICQ)) {
+                    return ProviderNames.ICQ;
+                }
+            }
+
+            return null;
+        }
+
+        /**
+         * This returns the provider category given a provider name.
+         *
+         * @param providername the provider name defined in {@link ProviderNames}.
+         * @return the provider category defined in {@link ProviderCategories}.
+         */
+        public static String getProviderCategoryFromName(String providername) {
+            if (providername != null) {
+                if (providername.equalsIgnoreCase(Im.ProviderNames.GTALK)) {
+                    return Im.ProviderCategories.GTALK;
+                } else if (providername.equalsIgnoreCase(Im.ProviderNames.AIM)) {
+                    return Im.ProviderCategories.AIM;
+                } else if (providername.equalsIgnoreCase(Im.ProviderNames.MSN)) {
+                    return Im.ProviderCategories.MSN;
+                } else if (providername.equalsIgnoreCase(Im.ProviderNames.YAHOO)) {
+                    return Im.ProviderCategories.YAHOO;
+                } else if (providername.equalsIgnoreCase(Im.ProviderNames.ICQ)) {
+                    return Im.ProviderCategories.ICQ;
+                }
+            }
+
+            return null;
+        }
+
+        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";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+    }
+
+    /**
+     * 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";
+    }
+
+    /**
+     * 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 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;
+        }
+
+        private static final String[] CONTACT_ETAG_PROJECTION = new String[] {
+                Im.ContactsEtag.ETAG    // 0
+        };
+
+        private static int COLUMN_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 {
+        int OUTGOING = 0;
+        int INCOMING = 1;
+        int PRESENCE_AVAILABLE = 2;
+        int PRESENCE_AWAY = 3;
+        int PRESENCE_DND = 4;
+        int PRESENCE_UNAVAILABLE = 5;
+        int CONVERT_TO_GROUPCHAT = 6;
+        int STATUS = 7;
+        int POSTPONED = 8;
+    }
+
+    /**
+     * 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";
+
+    }
+
+    /**
+     * Columns shared between the IM and contacts presence tables
+     */
+    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: TEXT</P>
+         */
+        String CONTACT_ID = "contact_id";
+
+        /**
+         * The GTalk JID resource. The value is a string.
+         */
+        String JID_RESOURCE = "jid_resource";
+
+        /**
+         * Whether this is a groupchat or not.
+         */
+        // TODO: remove this column since we already have a tag in contacts
+        // table to indicate it's a group chat.
+        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";
+    }
+
+    /**
+     * 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");
+    }
+}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
new file mode 100644
index 0000000..d99ad36
--- /dev/null
+++ b/core/java/android/provider/MediaStore.java
@@ -0,0 +1,1224 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 + "/";
+    
+    /**
+     * Standard Intent action that can be sent to have the media application
+     * capture an image and return it.  The image is returned as a Bitmap
+     * object in the extra field.
+     * @hide
+     */
+    public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
+    
+    /** 
+     * 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
+             * <P>Type: TEXT</P>
+             */
+            public static final String BUCKET_ID = "bucket_id";
+            
+            /**
+             * The bucket display name of the image
+             * <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 = "name ASC";
+       }
+
+        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 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 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.
+             * 
+             */
+            public static final String RECORD_SOUND_ACTION = 
+                    "android.provider.MediaStore.RECORD_SOUND";
+        }
+    
+        /**
+         * 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";
+
+            /**
+             * 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 = "name ASC";
+
+        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";
+        }
+
+        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..1439b26
--- /dev/null
+++ b/core/java/android/provider/SearchRecentSuggestions.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     */
+    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..6897bd5
--- /dev/null
+++ b/core/java/android/provider/Settings.java
@@ -0,0 +1,2073 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Intent;
+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.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+
+
+/**
+ * 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 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 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 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 sync settings.
+     * <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_SYNC_SETTINGS =
+            "android.settings.SYNC_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.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;
+
+        /**
+         * 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 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 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.
+         * 0=no  1=yes
+         */
+        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";
+
+        /**
+         * Whether the Wi-Fi should be on.  Only the Wi-Fi service should touch this.
+         */
+        public static final String WIFI_ON = "wifi_on";
+
+        /**
+         * 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 #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";
+
+        /**
+         * 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 watchdog is enabled.
+         */
+        public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
+
+        /**
+         * 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 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 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 timeout per ping.
+         */
+        public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
+
+        /**
+         * The delay between pings.
+         */
+        public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms";
+
+        /**
+         * 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 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 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 delay between background checks.
+         */
+        public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
+                "wifi_watchdog_background_check_delay_ms";
+        
+        /**
+         * 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";
+
+        /**
+         * User preference for which network(s) should be used. Only the
+         * connectivity service should touch this.
+         */
+        public static final String NETWORK_PREFERENCE = "network_preference";
+
+        /**
+         * Whether bluetooth is enabled/disabled
+         * 0=disabled. 1=enabled.
+         */
+        public static final String BLUETOOTH_ON = "bluetooth_on";
+
+        /**
+         * 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 the device has been provisioned (0 = false, 1 = true)
+         */
+        public static final String DEVICE_PROVISIONED = "device_provisioned";
+
+        /**
+         * Whether lock pattern is visible as user enters (0 = false, 1 = true)
+         */
+        public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
+
+
+        /**
+         * 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";
+
+        /**
+         * Comma-separated list of location providers that activities may access.
+         */
+        public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
+
+        /**
+         * Whether or not data roaming is enabled. (0 = false, 1 = true)
+         */
+        public static final String DATA_ROAMING = "data_roaming";
+
+        /**
+         * 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";
+
+        /**
+         * 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
+        };
+
+        /**
+         * 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";
+        /**
+         * USB Mass Storage Enabled
+         */
+        public static final String USB_MASS_STORAGE_ENABLED =
+                "usb_mass_storage_enabled";
+
+        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";
+
+        /**
+         * Host name and port for a user-selected proxy.
+         */
+        public static final String HTTP_PROXY = "http_proxy";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+        /**
+         * The Logging ID (a unique 64-bit value) as a hex string.
+         * Used as a pseudonymous identifier for logging.
+         */
+        public static final String LOGGING_ID = "logging_id";
+
+        /**
+         * 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 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";
+
+        /**
+         * 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";
+
+        public static final String PARENTAL_CONTROL_ENABLED =
+            "parental_control_enabled";
+
+        public static final String PARENTAL_CONTROL_REDIRECT_URL =
+            "parental_control_redirect_url";
+
+        public static final String PARENTAL_CONTROL_LAST_UPDATE =
+          "parental_control_last_update";
+
+        /**
+         * Whether ADB is enabled.
+         */
+        public static final String ADB_ENABLED = "adb_enabled";
+
+        /**
+         * 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";
+    }
+
+
+    /**
+     * 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.
+     * @hide
+     */
+    public static final class Gservices extends NameValueTable {
+        public static final String SYS_PROP_SETTING_VERSION = "sys.settings_gservices_version";
+
+        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 - "most viewed" url
+         */
+        public static final String YOUTUBE_MOST_VIEWED_URL
+                = "youtube_most_viewed_url";
+
+        /**
+         * YouTube - "most recent" url
+         */
+        public static final String YOUTUBE_MOST_RECENT_URL
+                = "youtube_most_recent_url";
+
+        /**
+         * YouTube - "top favorites" url
+         */
+        public static final String YOUTUBE_TOP_FAVORITES_URL
+                = "youtube_top_favorites_url";
+
+        /**
+         * YouTube - "most discussed" url
+         */
+        public static final String YOUTUBE_MOST_DISCUSSED_URL
+                = "youtube_most_discussed_url";
+
+        /**
+         * YouTube - "most responded" url
+         */
+        public static final String YOUTUBE_MOST_RESPONDED_URL
+                = "youtube_most_responded_url";
+
+        /**
+         * YouTube - "most linked" url
+         */
+        public static final String YOUTUBE_MOST_LINKED_URL
+                = "youtube_most_linked_url";
+
+        /**
+         * YouTube - "top rated" url
+         */
+        public static final String YOUTUBE_TOP_RATED_URL
+                = "youtube_top_rated_url";
+
+        /**
+         * YouTube - "recently featured" url
+         */
+        public static final String YOUTUBE_RECENTLY_FEATURED_URL
+                = "youtube_recently_featured_url";
+
+        /**
+         * YouTube - my uploaded videos
+         */
+        public static final String YOUTUBE_MY_VIDEOS_URL
+                = "youtube_my_videos_url";
+
+        /**
+         * YouTube - "my favorite" videos url
+         */
+        public static final String YOUTUBE_MY_FAVORITES_URL
+                = "youtube_my_favorites_url";
+
+        /**
+         * YouTube - "by author" videos url -- used for My videos
+         */
+        public static final String YOUTUBE_BY_AUTHOR_URL
+                = "youtube_by_author_url";
+
+        /**
+         * YouTube - save a video to favorite videos url
+         */
+        public static final String YOUTUBE_SAVE_TO_FAVORITES_URL
+                = "youtube_save_to_favorites_url";
+
+        /**
+         * YouTube - "mobile" videos url
+         */
+        public static final String YOUTUBE_MOBILE_VIDEOS_URL
+                = "youtube_mobile_videos_url";
+
+        /**
+         * YouTube - search videos url
+         */
+        public static final String YOUTUBE_SEARCH_URL
+                = "youtube_search_url";
+
+        /**
+         * YouTube - category search videos url
+         */
+        public static final String YOUTUBE_CATEGORY_SEARCH_URL
+                = "youtube_category_search_url";
+
+        /**
+         * YouTube - url to get the list of categories
+         */
+        public static final String YOUTUBE_CATEGORY_LIST_URL
+                = "youtube_category_list_url";
+
+        /**
+         * YouTube - related videos url
+         */
+        public static final String YOUTUBE_RELATED_VIDEOS_URL
+                = "youtube_related_videos_url";
+
+        /**
+         * YouTube - individual video url
+         */
+        public static final String YOUTUBE_INDIVIDUAL_VIDEO_URL
+                = "youtube_individual_video_url";
+
+        /**
+         * YouTube - user's playlist url
+         */
+        public static final String YOUTUBE_MY_PLAYLISTS_URL
+                = "youtube_my_playlists_url";
+
+        /**
+         * YouTube - user's subscriptions url
+         */
+        public static final String YOUTUBE_MY_SUBSCRIPTIONS_URL
+                = "youtube_my_subscriptions_url";
+
+        /**
+         * YouTube - the url we use to contact YouTube to get a device id
+         */
+        public static final String YOUTUBE_REGISTER_DEVICE_URL
+                = "youtube_register_device_url";
+
+        /**
+         * YouTube - the flag to indicate whether to use proxy
+         */
+        public static final String YOUTUBE_USE_PROXY
+                = "youtube_use_proxy";
+
+        /**
+         * Event tags from the kernel event log to upload during checkin.
+         */
+        public static final String CHECKIN_EVENTS = "checkin_events";
+
+        /**
+         * The interval (in seconds) between periodic checkin attempts.
+         */
+        public static final String CHECKIN_INTERVAL = "checkin_interval";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+        /**
+         * 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";
+
+
+        /**
+         * 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 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 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";
+
+        /**
+         * The interval in milliseconds at which we forcefully release the
+         * transition-to-mobile-data wake lock.
+         */
+        public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+                "wifi_mobile_data_transition_wakelock_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.
+         */
+        public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
+
+        /**
+         * @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.
+         * <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 #labelForFolder}) 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.
+         * @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 labelForFolder(Resources r, String folder) {
+            return folder;
+        }
+    }
+
+    /**
+     * 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..b889293
--- /dev/null
+++ b/core/java/android/provider/Sync.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+        // 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";
+                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 devices 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_";
+
+        /**
+         * 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);
+        }
+
+        /**
+         * 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 tickle xmpp connection
+         * should be established.
+         *
+         * @param contentResolver the ContentResolver to use to access the settings table
+         * @param flag true if the tickle xmpp connection should be established
+         */
+        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 tickle xmpp connection should be established.
+             *
+             * @param flag true if the tickle xmpp connection should be established
+             */
+            public void setListenForNetworkTickles(boolean flag) {
+                Settings.setListenForNetworkTickles(mContentResolver, flag);
+            }
+
+            /**
+             * Check if the tickle xmpp connection should be established
+             * @return true if it should be stablished
+             */
+            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..776a266
--- /dev/null
+++ b/core/java/android/provider/Telephony.java
@@ -0,0 +1,1694 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.telephony.CallerInfo;
+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.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+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);
+        }
+
+        /**
+         * 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) {
+            /*
+             * The '@' char isn't a valid char in phone numbers. However, in SMS
+             * messages sent by carrier, the originating-address can contain
+             * non-dialable alphanumeric chars. For the purpose of thread id
+             * grouping, we don't care about those. We only care about the
+             * legitmate/dialable phone numbers (which we use the special phone
+             * number comparison) and email addresses (which we do straight up
+             * string comparison).
+             */
+            return (address != null) && (address.indexOf('@') != -1);
+        }
+
+        /**
+         * Formats an address for displaying, doing a phone number lookup in the
+         * Address Book, etc.
+         *
+         * @param context the context to use
+         * @param address the address to format
+         * @return a nicely formatted version of the sender to display
+         */
+        public static String getDisplayAddress(Context context, String address) {
+            String result;
+            int index;
+            if (isEmailAddress(address)) {
+                index = address.indexOf('@');
+                if (index != -1) {
+                    result = address.substring(0, index);
+                } else {
+                    result = address;
+                }
+            } else {
+                result = CallerInfo.getCallerId(context, address);
+            }
+            return result;
+        }
+
+        /**
+         * 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]
+         */
+        private static final Pattern NAME_ADDR_EMAIL_PATTERN =
+                Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+        /**
+         * quoted-string   =       [CFWS]
+         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+         *                         [CFWS]
+         */
+        private 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();
+        }
+
+        /**
+         * Formats an address for displaying, doing a phone number lookup in the
+         * Address Book, etc.
+         *
+         * @param context the context to use
+         * @param address the address to format
+         * @return a nicely formatted version of the sender to display
+         */
+        public static String getDisplayAddress(Context context, String address) {
+            if (address == null) {
+                return "";
+            }
+
+            String localNumber = TelephonyManager.getDefault().getLine1Number();
+            String[] values = address.split(";");
+            String result = "";
+            for (int i = 0; i < values.length; i++) {
+                if (values[i].length() > 0) {
+                    if (PhoneNumberUtils.compare(values[i], localNumber)) {
+                        result = result + ";"
+                                    + context.getString(com.android.internal.R.string.me);
+                    } else if (isEmailAddress(values[i])) {
+                        result = result + ";" + getDisplayName(context, values[i]);
+                    } else {
+                        result = result + ";" + CallerInfo.getCallerId(context, values[i]);
+                    }
+                }
+            }
+
+            if (result.length() > 0) {
+                // Skip the first ';'
+                return result.substring(1);
+            }
+            return result;
+        }
+
+        private static String getEmailDisplayName(String displayString) {
+            Matcher match = QUOTED_STRING_PATTERN.matcher(displayString);
+            if (match.matches()) {
+                return match.group(1);
+            }
+
+            return displayString;
+        }
+
+        private static String getDisplayName(Context context, String email) {
+            Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(email);
+            if (match.matches()) {
+                // email has display name
+                return getEmailDisplayName(match.group(1));
+            }
+
+            Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+                    Contacts.ContactMethods.CONTENT_EMAIL_URI,
+                    new String[] { Contacts.ContactMethods.NAME },
+                    Contacts.ContactMethods.DATA + " = \'" + email + "\'",
+                    null, null);
+
+            if (cursor != null) {
+                try {
+                    int columnIndex = cursor.getColumnIndexOrThrow(
+                            Contacts.ContactMethods.NAME);
+                    while (cursor.moveToNext()) {
+                        String name = cursor.getString(columnIndex);
+                        if (!TextUtils.isEmpty(name)) {
+                            return name;
+                        }
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+            return email;
+        }
+
+        /**
+         * 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 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/package.html b/core/java/android/provider/package.html
new file mode 100644
index 0000000..a553592
--- /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}devel/data.html">Reading and Writing Persistent Data</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..a7221ae
--- /dev/null
+++ b/core/java/android/security/Md5MessageDigest.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.security;
+
+/**
+ * This is a temporary class to provide SHA-1 hash.
+ * It's not meant to be correct, and eventually doesn't belong in java.security
+ */
+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..93040b9
--- /dev/null
+++ b/core/java/android/security/MessageDigest.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.security;
+
+import java.security.NoSuchAlgorithmException;
+
+public abstract class MessageDigest 
+{    
+    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();
+    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..3b3fd6a
--- /dev/null
+++ b/core/java/android/security/Sha1MessageDigest.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.security;
+
+/**
+ * This is a temporary class to provide SHA-1 hash.
+ * It's not meant to be correct, and eventually doesn't belong in java.security
+ */
+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..26b8a32
--- /dev/null
+++ b/core/java/android/security/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+{@hide}
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
new file mode 100644
index 0000000..10f9f7c
--- /dev/null
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -0,0 +1,1175 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.BluetoothHeadset;  // just for dump()
+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.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemService;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class BluetoothDeviceService extends IBluetoothDevice.Stub {
+    private static final String TAG = "BluetoothDeviceService";
+    private int mNativeData;
+    private Context mContext;
+    private BluetoothEventLoop mEventLoop;
+    private IntentFilter mIntentFilter;
+    private boolean mIsAirplaneSensitive;
+    private volatile boolean mIsEnabled;  // local cache of isEnabledNative()
+    private boolean mIsDiscovering;
+
+    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);
+        mIsDiscovering = false;
+        mEventLoop = new BluetoothEventLoop(mContext, this);
+        registerForAirplaneMode();
+        
+        disableEsco();  // TODO: enable eSCO support once its fully supported
+    }
+    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() {
+        checkPermissionBluetooth();
+        return mIsEnabled;
+    }
+    private native int isEnabledNative();
+
+    /**
+     * Disable bluetooth. Returns true on success.
+     */
+    public synchronized boolean disable() {
+        checkPermissionBluetoothAdmin();
+
+        if (mEnableThread != null && mEnableThread.isAlive()) {
+            return false;
+        }
+        if (!mIsEnabled) {
+            return true;
+        }
+        mEventLoop.stop();
+        disableNative();
+        mIsEnabled = false;
+        mIsDiscovering = false;
+        Intent intent = new Intent(BluetoothIntent.DISABLED_ACTION);
+        mContext.sendBroadcast(intent);
+        return true;
+    }
+
+    /**
+     * Enable this Bluetooth device, asynchronously.
+     * This turns on/off the underlying hardware.
+     *
+     * @return True on success (so far), guarenteeing the callback with be
+     * notified when complete.
+     */
+    public synchronized boolean enable(IBluetoothDeviceCallback callback) {
+        checkPermissionBluetoothAdmin();
+
+        // 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);
+        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;
+        public EnableThread(IBluetoothDeviceCallback callback) {
+            mEnableCallback = callback;
+        }
+        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;
+                mIsDiscovering = false;
+                Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION);
+                mContext.sendBroadcast(intent);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000);
+            }
+            mEnableThread = null;
+        }
+    };
+
+    private native int enableNative();
+    private native int disableNative();
+
+    public synchronized String getAddress() {
+        checkPermissionBluetooth();
+        return getAddressNative();
+    }
+    private native String getAddressNative();
+
+    public synchronized String getName() {
+        checkPermissionBluetooth();
+        return getNameNative();
+    }
+    private native String getNameNative();
+
+    public synchronized boolean setName(String name) {
+        checkPermissionBluetoothAdmin();
+        if (name == null) {
+            return false;
+        }
+        // hcid handles persistance of the bluetooth name
+        return setNameNative(name);
+    }
+    private native boolean setNameNative(String name);
+
+    public synchronized String[] listBondings() {
+        checkPermissionBluetooth();
+        return listBondingsNative();
+    }
+    private native String[] listBondingsNative();
+
+    public synchronized String getMajorClass() {
+        checkPermissionBluetooth();
+        return getMajorClassNative();
+    }
+    private native String getMajorClassNative();
+
+    public synchronized String getMinorClass() {
+        checkPermissionBluetooth();
+        return getMinorClassNative();
+    }
+    private native String getMinorClassNative();
+
+    /**
+     * 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) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteNameNative(address);
+    }
+    private native String getRemoteNameNative(String address);
+
+    /* pacakge */ native String getAdapterPathNative();
+
+    /**
+     * Initiate a remote-device-discovery procedure.  This procedure may be
+     * canceled by calling {@link #stopDiscovery}.  Remote-device discoveries
+     * are returned as intents
+     * <p>
+     * Typically, when a remote device is found, your
+     * android.bluetooth.DiscoveryEventNotifier#notifyRemoteDeviceFound
+     *  method will be invoked, and subsequently, your
+     * android.bluetooth.RemoteDeviceEventNotifier#notifyRemoteNameUpdated
+     * will tell you the user-friendly name of the remote device.  However,
+     * it is possible that the name update may fail for various reasons, so you
+     * should display the device's Bluetooth address as soon as you get a
+     * notifyRemoteDeviceFound event, and update the name when you get the
+     * remote name.
+     *
+     * @return true if discovery has started,
+     *         false otherwise.
+     */
+    public synchronized boolean startDiscovery(boolean resolveNames) {
+        checkPermissionBluetoothAdmin();
+        return startDiscoveryNative(resolveNames);
+    }
+    private native boolean startDiscoveryNative(boolean resolveNames);
+
+    /**
+     * Cancel a remote-device discovery.
+     *
+     * Note: you may safely call this method even when discovery has not been
+     *       started.
+     */
+    public synchronized boolean cancelDiscovery() {
+        checkPermissionBluetoothAdmin();
+        return cancelDiscoveryNative();
+    }
+    private native boolean cancelDiscoveryNative();
+
+    public synchronized boolean isDiscovering() {
+        checkPermissionBluetooth();
+        return mIsDiscovering;
+    }
+
+    /* package */ void setIsDiscovering(boolean isDiscovering) {
+        mIsDiscovering = isDiscovering;
+    }
+
+    public synchronized boolean startPeriodicDiscovery() {
+        checkPermissionBluetoothAdmin();
+        return startPeriodicDiscoveryNative();
+    }
+    private native boolean startPeriodicDiscoveryNative();
+
+    public synchronized boolean stopPeriodicDiscovery() {
+        checkPermissionBluetoothAdmin();
+        return stopPeriodicDiscoveryNative();
+    }
+    private native boolean stopPeriodicDiscoveryNative();
+
+    public synchronized boolean isPeriodicDiscovery() {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetoothAdmin();
+        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() {
+        checkPermissionBluetooth();
+        return getDiscoverableTimeoutNative();
+    }
+    private native int getDiscoverableTimeoutNative();
+
+    public synchronized boolean isAclConnected(String address) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return isConnectedNative(address);
+    }
+    private native boolean isConnectedNative(String address);
+
+    /**
+     * Detetermines whether this device is connectable (that is, whether remote
+     * devices can connect to it.)
+     * <p>
+     * Note: A Bluetooth adapter has separate connectable and discoverable
+     *       states, and you could have any combination of those.  Although
+     *       any combination is possible (such as discoverable but not
+     *       connectable), we restrict the possible combinations to one of
+     *       three possibilities: discoverable and connectable, connectable
+     *       but not discoverable, and neither connectable nor discoverable.
+     *
+     * @return true if this adapter is connectable
+     *         false otherwise
+     *
+     * @see #isDiscoverable
+     * @see #getMode
+     * @see #setMode
+     */
+    public synchronized boolean isConnectable() {
+        checkPermissionBluetooth();
+        return isConnectableNative();
+    }
+    private native boolean isConnectableNative();
+
+    /**
+     * Detetermines whether this device is discoverable.
+     *
+     * Note: a Bluetooth adapter has separate connectable and discoverable
+     *       states, and you could have any combination of those.  Although
+     *       any combination is possible (such as discoverable but not
+     *       connectable), we restrict the possible combinations to one of
+     *       three possibilities: discoverable and connectable, connectable
+     *       but not discoverable, and neither connectable nor discoverable.
+     *
+     * @return true if this adapter is discoverable
+     *         false otherwise
+     *
+     * @see #isConnectable
+     * @see #getMode
+     * @see #setMode
+     */
+    public synchronized boolean isDiscoverable() {
+        checkPermissionBluetooth();
+        return isDiscoverableNative();
+    }
+    private native boolean isDiscoverableNative();
+
+    /**
+     * Determines which one of three modes this adapter is in: discoverable and
+     * connectable, not discoverable but connectable, or neither.
+     *
+     * @return Mode enumeration containing the current mode.
+     *
+     * @see #setMode
+     */
+    public synchronized int getMode() {
+        checkPermissionBluetooth();
+        String mode = getModeNative();
+        if (mode == null) {
+            return BluetoothDevice.MODE_UNKNOWN;
+        }
+        if (mode.equalsIgnoreCase("off")) {
+            return BluetoothDevice.MODE_OFF;
+        }
+        else if (mode.equalsIgnoreCase("connectable")) {
+            return BluetoothDevice.MODE_CONNECTABLE;
+        }
+        else if (mode.equalsIgnoreCase("discoverable")) {
+            return BluetoothDevice.MODE_DISCOVERABLE;
+        }
+        else {
+            return BluetoothDevice.MODE_UNKNOWN;
+        }
+    }
+    private native String getModeNative();
+
+    /**
+     * Set the discoverability and connectability mode of this adapter.  The
+     * possibilities are discoverable and connectable (MODE_DISCOVERABLE),
+     * connectable but not discoverable (MODE_CONNECTABLE), and neither
+     * (MODE_OFF).
+     *
+     * Note: MODE_OFF does not mean that the adapter is physically off.  It
+     *       may be neither discoverable nor connectable, but it could still
+     *       initiate outgoing connections, or could participate in a
+     *       connection initiated by a remote device before its mode was set
+     *       to MODE_OFF.
+     *
+     * @param mode the new mode
+     * @see #getMode
+     */
+    public synchronized boolean setMode(int mode) {
+        checkPermissionBluetoothAdmin();
+        switch (mode) {
+        case BluetoothDevice.MODE_OFF:
+            return setModeNative("off");
+        case BluetoothDevice.MODE_CONNECTABLE:
+            return setModeNative("connectable");
+        case BluetoothDevice.MODE_DISCOVERABLE:
+            return setModeNative("discoverable");
+        }
+        return false;
+    }
+    private native boolean setModeNative(String mode);
+
+    /**
+     * Retrieves the alias of a remote device.  The alias is a local feature,
+     * and allows us to associate a name with a remote device that is different
+     * from that remote device's user-friendly name.  The remote device knows
+     * nothing about this.  The alias can be changed with
+     * {@link #setRemoteAlias}, and it may be removed with
+     * {@link #clearRemoteAlias}
+     *
+     * @param address Bluetooth address of remote device.
+     *
+     * @return The alias of the remote device.
+     */
+    public synchronized String getRemoteAlias(String address) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteAliasNative(address);
+    }
+    private native String getRemoteAliasNative(String address);
+
+    /**
+     * Changes the alias of a remote device.  The alias is a local feature,
+     * from that remote device's user-friendly name.  The remote device knows
+     * nothing about this.  The alias can be retrieved with
+     * {@link #getRemoteAlias}, and it may be removed with
+     * {@link #clearRemoteAlias}.
+     *
+     * @param address Bluetooth address of remote device
+     * @param alias Alias for the remote device
+     */
+    public synchronized boolean setRemoteAlias(String address, String alias) {
+        checkPermissionBluetoothAdmin();
+        if (alias == null || !BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return setRemoteAliasNative(address, alias);
+    }
+    private native boolean setRemoteAliasNative(String address, String alias);
+
+    /**
+     * Removes the alias of a remote device.  The alias is a local feature,
+     * from that remote device's user-friendly name.  The remote device knows
+     * nothing about this.  The alias can be retrieved with
+     * {@link #getRemoteAlias}.
+     *
+     * @param address Bluetooth address of remote device
+     */
+    public synchronized boolean clearRemoteAlias(String address) {
+        checkPermissionBluetoothAdmin();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return clearRemoteAliasNative(address);
+    }
+    private native boolean clearRemoteAliasNative(String address);
+
+    public synchronized boolean disconnectRemoteDeviceAcl(String address) {
+        checkPermissionBluetoothAdmin();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return disconnectRemoteDeviceNative(address);
+    }
+    private native boolean disconnectRemoteDeviceNative(String address);
+
+    private static final int MAX_OUTSTANDING_ASYNC = 32;
+    /**
+     * This method initiates a Bonding request to a remote device.
+     *
+     *
+     * @param address The Bluetooth address of the remote device
+     *
+     * @see #createBonding
+     * @see #cancelBondingProcess
+     * @see #removeBonding
+     * @see #hasBonding
+     * @see #listBondings
+     *
+     * @see android.bluetooth.PasskeyAgent
+     */
+    public synchronized boolean createBonding(String address, IBluetoothDeviceCallback callback) {
+        checkPermissionBluetoothAdmin();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+
+        HashMap<String, IBluetoothDeviceCallback> callbacks = mEventLoop.getCreateBondingCallbacks();
+        if (callbacks.containsKey(address)) {
+            Log.w(TAG, "createBonding() already in progress for " + address);
+            return false;
+        }
+
+        // Protect from malicious clients - limit number of outstanding requests
+        if (callbacks.size() > MAX_OUTSTANDING_ASYNC) {
+            Log.w(TAG, "Too many outstanding bonding requests, dropping request for " + address);
+            return false;
+        }
+
+        callbacks.put(address, callback);
+        if (!createBondingNative(address, 60000 /* 1 minute */)) {
+            callbacks.remove(address);
+            return false;
+        }
+        return true;
+    }
+    private native boolean createBondingNative(String address, int timeout_ms);
+
+    /**
+     * This method cancels a pending bonding request.
+     *
+     * @param address The Bluetooth address of the remote device to which a
+     *        bonding request has been initiated.
+     *
+     * Note: When a request is canceled, method
+     *       {@link CreateBondingResultNotifier#notifyAuthenticationFailed}
+     *       will be called on the object passed to method
+     *       {@link #createBonding}.
+     *
+     * Note: it is safe to call this method when there is no outstanding
+     *       bonding request.
+     *
+     * @see #createBonding
+     * @see #cancelBondingProcess
+     * @see #removeBonding
+     * @see #hasBonding
+     * @see #listBondings
+     */
+    public synchronized boolean cancelBondingProcess(String address) {
+        checkPermissionBluetoothAdmin();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return cancelBondingProcessNative(address);
+    }
+    private native boolean cancelBondingProcessNative(String address);
+
+    /**
+     * This method removes a bonding to a remote device.  This is a local
+     * operation only, resulting in this adapter "forgetting" the bonding
+     * information about the specified remote device.  The other device itself
+     * does not know what the bonding has been torn down.  The next time either
+     * device attemps to connect to the other, the connection will fail, and
+     * the pairing procedure will have to be re-initiated.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @see #createBonding
+     * @see #cancelBondingProcess
+     * @see #removeBonding
+     * @see #hasBonding
+     * @see #listBondings
+     */
+    public synchronized boolean removeBonding(String address) {
+        checkPermissionBluetoothAdmin();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return removeBondingNative(address);
+    }
+    private native boolean removeBondingNative(String address);
+
+    public synchronized boolean hasBonding(String address) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        return hasBondingNative(address);
+    }
+    private native boolean hasBondingNative(String address);
+
+    public synchronized String[] listAclConnections() {
+        checkPermissionBluetooth();
+        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() {
+        checkPermissionBluetooth();
+        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() {
+        checkPermissionBluetooth();
+        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() {
+        checkPermissionBluetooth();
+        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() {
+        checkPermissionBluetooth();
+        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() {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return lastUsedNative(address);
+    }
+    private native String lastUsedNative(String address);
+
+    /**
+     * Gets the major device class of the specified device.
+     * Example: "computer"
+     *
+     * Note:  This is simply a string desciption of the major class of the
+     *        device-class information, which is returned as a 32-bit value
+     *        during device discovery.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @return remote-device major class
+     *
+     * @see #getRemoteClass
+     */
+    public synchronized String getRemoteMajorClass(String address) {
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+        checkPermissionBluetooth();
+            return null;
+        }
+        return getRemoteMajorClassNative(address);
+    }
+    private native String getRemoteMajorClassNative(String address);
+
+    /**
+     * Gets the minor device class of the specified device.
+     * Example: "laptop"
+     *
+     * Note:  This is simply a string desciption of the minor class of the
+     *        device-class information, which is returned as a 32-bit value
+     *        during device discovery.
+     *
+     * @param address The Bluetooth address of the remote device.
+     *
+     * @return remote-device minor class
+     *
+     * @see #getRemoteClass
+     */
+    public synchronized String getRemoteMinorClass(String address) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteMinorClassNative(address);
+    }
+    private native String getRemoteMinorClassNative(String address);
+
+    /**
+     * Gets the service classes of the specified device.
+     * Example: ["networking", "object transfer"]
+     *
+     * @return a String array with the descriptions of the service classes.
+     *
+     * @see #getRemoteClass
+     */
+    public synchronized String[] getRemoteServiceClasses(String address) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteServiceClassesNative(address);
+    }
+    private native String[] getRemoteServiceClassesNative(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)) {
+        checkPermissionBluetooth();
+            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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetooth();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return null;
+        }
+        return getRemoteServiceRecordNative(address, handle);
+    }
+    private native byte[] getRemoteServiceRecordNative(String address, int handle);
+
+    // AIDL does not yet support short's
+    public synchronized boolean getRemoteServiceChannel(String address, int uuid16,
+            IBluetoothDeviceCallback callback) {
+        checkPermissionBluetooth();
+        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) {
+        checkPermissionBluetoothAdmin();
+        if (pin == null || pin.length <= 0 || pin.length > 16 ||
+            !BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        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) {
+        checkPermissionBluetoothAdmin();
+        if (!BluetoothDevice.checkBluetoothAddress(address)) {
+            return false;
+        }
+        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.System.getInt(resolver, Settings.System.BLUETOOTH_ON, 0) > 0) {
+                    if (enabled) {
+                        enable(null);
+                    } else {
+                        disable();
+                    }
+                }
+            }
+        }
+    };
+
+    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;
+    }
+
+    private static final String BLUETOOTH_ADMIN = android.Manifest.permission.BLUETOOTH_ADMIN;
+    private static final String BLUETOOTH = android.Manifest.permission.BLUETOOTH;
+
+    private void checkPermissionBluetoothAdmin() {
+        if (mContext.checkCallingOrSelfPermission(BLUETOOTH_ADMIN) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires BLUETOOTH_ADMIN permission");
+        }
+    }
+
+    private void checkPermissionBluetooth() {
+        if (mContext.checkCallingOrSelfPermission(BLUETOOTH_ADMIN) !=
+                PackageManager.PERMISSION_GRANTED &&
+                mContext.checkCallingOrSelfPermission(BLUETOOTH) !=
+                PackageManager.PERMISSION_GRANTED ) {
+            throw new SecurityException("Requires BLUETOOTH or BLUETOOTH_ADMIN permission");
+        }
+    }
+
+    private static final String DISABLE_ESCO_PATH = "/sys/module/sco/parameters/disable_esco";
+    private static void disableEsco() {
+        try {
+            FileWriter file = new FileWriter(DISABLE_ESCO_PATH);
+            file.write("Y");
+            file.close();
+        } catch (FileNotFoundException e) {
+        } catch (IOException e) {}
+    }
+
+    @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);
+
+            pw.println("\n--Bondings--");
+            String[] addresses = listBondings();
+            for (String address : addresses) {
+                String name = getRemoteName(address);
+                pw.println(address + " (" + name + ")");
+            }
+
+            pw.println("\n--Current ACL Connections--");
+            addresses = listAclConnections();
+            for (String address : addresses) {
+                String name = getRemoteName(address);
+                pw.println(address + " (" + name + ")");
+            }
+
+            pw.println("\n--Known Devices--");
+            addresses = listRemoteDevices();
+            for (String address : addresses) {
+                String name = getRemoteName(address);
+                pw.println(address + " (" + name + ")");
+            }
+
+            // 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);
+    }
+}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
new file mode 100644
index 0000000..5722f51
--- /dev/null
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.Thread;
+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, IBluetoothDeviceCallback> mCreateBondingCallbacks;
+    private HashMap<String, Integer> mPasskeyAgentRequestData;
+    private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks;
+    private BluetoothDeviceService mBluetoothService;
+
+    private Context mContext;
+
+    static { classInitNative(); }
+    private static native void classInitNative();
+
+    /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) {
+        mBluetoothService = bluetoothService;
+        mContext = context;
+        mCreateBondingCallbacks = new HashMap();
+        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> getCreateBondingCallbacks() {
+        return mCreateBondingCallbacks;
+    }
+    /* 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;
+    }
+
+    public void onModeChanged(String mode) {
+        Intent intent = new Intent(BluetoothIntent.MODE_CHANGED_ACTION);
+        int intMode = BluetoothDevice.MODE_UNKNOWN;
+        if (mode.equalsIgnoreCase("off")) {
+            intMode = BluetoothDevice.MODE_OFF;
+        }
+        else if (mode.equalsIgnoreCase("connectable")) {
+            intMode = BluetoothDevice.MODE_CONNECTABLE;
+        }
+        else if (mode.equalsIgnoreCase("discoverable")) {
+            intMode = BluetoothDevice.MODE_DISCOVERABLE;
+        }
+        intent.putExtra(BluetoothIntent.MODE, intMode);
+        mContext.sendBroadcast(intent);
+    }
+
+    public void onDiscoveryStarted() {
+        mBluetoothService.setIsDiscovering(true);
+        Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
+        mContext.sendBroadcast(intent);
+    }
+    public void onDiscoveryCompleted() {
+        mBluetoothService.setIsDiscovering(false);
+        Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
+        mContext.sendBroadcast(intent);
+    }
+
+    public void onPairingRequest() {
+        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
+        mContext.sendBroadcast(intent);
+    }
+    public void onPairingCancel() {
+        Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
+        mContext.sendBroadcast(intent);
+    }
+
+    public 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);
+    }
+    public void onRemoteDeviceDisappeared(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public 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);
+    }
+    public void onRemoteDeviceConnected(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public void onRemoteDeviceDisconnectRequested(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public void onRemoteDeviceDisconnected(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public 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);
+    }
+    public void onRemoteNameFailed(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public 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);
+    }
+    public void onRemoteAliasChanged(String address, String alias) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_ALIAS_CHANGED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        intent.putExtra(BluetoothIntent.ALIAS, alias);
+        mContext.sendBroadcast(intent);
+    }
+    public void onRemoteAliasCleared(String address) {
+        Intent intent = new Intent(BluetoothIntent.REMOTE_ALIAS_CLEARED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void onCreateBondingResult(String address, boolean result) {
+        IBluetoothDeviceCallback callback = mCreateBondingCallbacks.get(address);
+        if (callback != null) {
+            try {
+                callback.onCreateBondingResult(address,
+                        result ? BluetoothDevice.RESULT_SUCCESS :
+                                 BluetoothDevice.RESULT_FAILURE);
+            } catch (RemoteException e) {}
+            mCreateBondingCallbacks.remove(address);
+        }
+    }
+    public void onBondingCreated(String address) {
+        Intent intent = new Intent(BluetoothIntent.BONDING_CREATED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public void onBondingRemoved(String address) {
+        Intent intent = new Intent(BluetoothIntent.BONDING_REMOVED_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+
+    public void onNameChanged(String name) {
+        Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
+        intent.putExtra(BluetoothIntent.NAME, name);
+        mContext.sendBroadcast(intent);
+    }
+
+    public void onPasskeyAgentRequest(String address, int nativeData) {
+        mPasskeyAgentRequestData.put(address, new Integer(nativeData));
+
+        Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    public void onPasskeyAgentCancel(String address) {
+        mPasskeyAgentRequestData.remove(address);
+
+        Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
+        intent.putExtra(BluetoothIntent.ADDRESS, address);
+        mContext.sendBroadcast(intent);
+    }
+    private void onGetRemoteServiceChannelResult(String address, int channel) {
+        IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
+        if (callback != null) {
+            try {
+                callback.onGetRemoteServiceChannelResult(address, channel);
+            } catch (RemoteException e) {}
+            mGetRemoteServiceChannelCallbacks.remove(address);
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/server/checkin/CheckinProvider.java b/core/java/android/server/checkin/CheckinProvider.java
new file mode 100644
index 0000000..86ece4a
--- /dev/null
+++ b/core/java/android/server/checkin/CheckinProvider.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.server.checkin;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.BaseColumns;
+import android.provider.Checkin;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * Content provider for the database used to store events and statistics
+ * while they wait to be uploaded by the checkin service.
+ */
+public class CheckinProvider extends ContentProvider {
+    /** Class identifier for logging. */
+    private static final String TAG = "CheckinProvider";
+
+    /** Filename of database (in /data directory). */
+    private static final String DATABASE_FILENAME = "checkin.db";
+
+    /** Version of database schema.  */
+    private static final int DATABASE_VERSION = 1;
+
+    /** Maximum number of events recorded. */
+    private static final int EVENT_LIMIT = 1000;
+
+    /** Maximum size of individual event data. */
+    private static final int EVENT_SIZE = 8192;
+
+    /** Maximum number of crashes recorded. */
+    private static final int CRASH_LIMIT = 25;
+
+    /** Maximum size of individual crashes recorded. */
+    private static final int CRASH_SIZE = 16384;
+
+    /** Permission required for access to the 'properties' database. */
+    private static final String PROPERTIES_PERMISSION =
+            "android.permission.ACCESS_CHECKIN_PROPERTIES";
+
+    /** Lock for stats read-modify-write update cycle (see {@link #insert}). */
+    private final Object mStatsLock = new Object();
+
+    /** The underlying SQLite database. */
+    private SQLiteOpenHelper mOpenHelper;
+
+    private static class OpenHelper extends SQLiteOpenHelper {
+        public OpenHelper(Context context) {
+            super(context, DATABASE_FILENAME, null, DATABASE_VERSION);
+
+            // The database used to live in /data/checkin.db.
+            File oldLocation = Environment.getDataDirectory();
+            File old = new File(oldLocation, DATABASE_FILENAME);
+            File file = context.getDatabasePath(DATABASE_FILENAME);
+
+            // Try to move the file to the new location.
+            // TODO: Remove this code before shipping.
+            if (old.exists() && !file.exists() && !old.renameTo(file)) {
+               Log.e(TAG, "Can't rename " + old + " to " + file);
+            }
+            if (old.exists() && !old.delete()) {
+               // Clean up the old data file in any case.
+               Log.e(TAG, "Can't remove " + old);
+            }
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + Checkin.Events.TABLE_NAME + " (" +
+                    BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Checkin.Events.TAG + " TEXT NOT NULL," +
+                    Checkin.Events.VALUE + " TEXT DEFAULT \"\"," +
+                    Checkin.Events.DATE + " INTEGER NOT NULL)");
+
+            db.execSQL("CREATE INDEX events_index ON " +
+                    Checkin.Events.TABLE_NAME + " (" +
+                    Checkin.Events.TAG + ")");
+
+            db.execSQL("CREATE TABLE " + Checkin.Stats.TABLE_NAME + " (" +
+                    BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Checkin.Stats.TAG + " TEXT UNIQUE," +
+                    Checkin.Stats.COUNT + " INTEGER DEFAULT 0," +
+                    Checkin.Stats.SUM + " REAL DEFAULT 0.0)");
+
+            db.execSQL("CREATE TABLE " + Checkin.Crashes.TABLE_NAME + " (" +
+                    BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Checkin.Crashes.DATA + " TEXT NOT NULL," +
+                    Checkin.Crashes.LOGS + " TEXT)");
+
+            db.execSQL("CREATE TABLE " + Checkin.Properties.TABLE_NAME + " (" +
+                    BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    Checkin.Properties.TAG + " TEXT UNIQUE ON CONFLICT REPLACE,"
+                    + Checkin.Properties.VALUE + " TEXT DEFAULT \"\")");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int old, int version) {
+            db.execSQL("DROP TABLE IF EXISTS " + Checkin.Events.TABLE_NAME);
+            db.execSQL("DROP TABLE IF EXISTS " + Checkin.Stats.TABLE_NAME);
+            db.execSQL("DROP TABLE IF EXISTS " + Checkin.Crashes.TABLE_NAME);
+            db.execSQL("DROP TABLE IF EXISTS " + Checkin.Properties.TABLE_NAME);
+            onCreate(db);
+        }
+    }
+
+    @Override public boolean onCreate() {
+        mOpenHelper = new OpenHelper(getContext());
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] select,
+            String where, String[] args, String sort) {
+        checkPermissions(uri);
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(uri.getPathSegments().get(0));
+        if (uri.getPathSegments().size() == 2) {
+            qb.appendWhere("_id=" + ContentUris.parseId(uri));
+        } else if (uri.getPathSegments().size() != 1) {
+            throw new IllegalArgumentException("Invalid query URI: " + uri);
+        }
+
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        Cursor cursor = qb.query(db, select, where, args, null, null, sort);
+        cursor.setNotificationUri(getContext().getContentResolver(), uri);
+        return cursor;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        checkPermissions(uri);
+        if (uri.getPathSegments().size() != 1) {
+            throw new IllegalArgumentException("Invalid insert URI: " + uri);
+        }
+
+        long id;
+        String table = uri.getPathSegments().get(0);
+        if (Checkin.Events.TABLE_NAME.equals(table)) {
+            id = insertEvent(values);
+        } else if (Checkin.Stats.TABLE_NAME.equals(table)) {
+            id = insertStats(values);
+        } else if (Checkin.Crashes.TABLE_NAME.equals(table)) {
+            id = insertCrash(values);
+        } else {
+            id = mOpenHelper.getWritableDatabase().insert(table, null, values);
+        }
+
+        if (id < 0) {
+            return null;
+        } else {
+            uri = ContentUris.withAppendedId(uri, id);
+            getContext().getContentResolver().notifyChange(uri, null);
+            return uri;
+        }
+    }
+
+    /**
+     * Insert an entry into the events table.
+     * Trims old events from the table to keep the size bounded.
+     * @param values to insert
+     * @return the row ID of the new entry
+     */
+    private long insertEvent(ContentValues values) {
+        String value = values.getAsString(Checkin.Events.VALUE);
+        if (value != null && value.length() > EVENT_SIZE) {
+            // Event values are readable text, so they can be truncated.
+            value = value.substring(0, EVENT_SIZE - 3) + "...";
+            values.put(Checkin.Events.VALUE, value);
+        }
+
+        if (!values.containsKey(Checkin.Events.DATE)) {
+            values.put(Checkin.Events.DATE, System.currentTimeMillis());
+        }
+
+        // TODO: Make this more efficient; don't do it on every insert.
+        // Also, consider keeping the most recent instance of every tag,
+        // and possibly update a counter when events are deleted.
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.execSQL("DELETE FROM " +
+                Checkin.Events.TABLE_NAME + " WHERE " +
+                Checkin.Events._ID + " IN (SELECT " +
+                Checkin.Events._ID + " FROM " +
+                Checkin.Events.TABLE_NAME + " ORDER BY " +
+                Checkin.Events.DATE + " DESC LIMIT -1 OFFSET " +
+                (EVENT_LIMIT - 1) + ")");
+        return db.insert(Checkin.Events.TABLE_NAME, null, values);
+    }
+
+    /**
+     * Add an entry into the stats table.
+     * For statistics, instead of just inserting a row into the database,
+     * we add the count and sum values to the existing values (if any)
+     * for the specified tag.  This must be done with a lock held,
+     * to avoid a race condition during the read-modify-write update.
+     * @param values to insert
+     * @return the row ID of the modified entry
+     */
+    private long insertStats(ContentValues values) {
+        synchronized (mStatsLock) {
+            String tag = values.getAsString(Checkin.Stats.TAG);
+            if (tag == null) {
+                throw new IllegalArgumentException("Tag required:" + values);
+            }
+
+            // Look for existing values with this tag.
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            Cursor cursor = db.query(false,
+                    Checkin.Stats.TABLE_NAME,
+                    new String[] {
+                            Checkin.Stats._ID,
+                            Checkin.Stats.COUNT,
+                            Checkin.Stats.SUM
+                    },
+                    Checkin.Stats.TAG + "=?",
+                    new String[] { tag },
+                    null, null, null, null /* limit */);
+
+            try {
+                if (cursor == null || !cursor.moveToNext()) {
+                    // This is a new statistic, insert it directly.
+                    return db.insert(Checkin.Stats.TABLE_NAME, null, values);
+                } else {
+                    // Depend on SELECT column order to avoid getColumnIndex()
+                    long id = cursor.getLong(0);
+                    int count = cursor.getInt(1);
+                    double sum = cursor.getDouble(2);
+
+                    Integer countAdd = values.getAsInteger(Checkin.Stats.COUNT);
+                    if (countAdd != null) count += countAdd.intValue();
+
+                    Double sumAdd = values.getAsDouble(Checkin.Stats.SUM);
+                    if (sumAdd != null) sum += sumAdd.doubleValue();
+
+                    if (count <= 0 && sum == 0.0) {
+                        // Updated to nothing: delete the row!
+                        cursor.deleteRow();
+                        getContext().getContentResolver().notifyChange(
+                                ContentUris.withAppendedId(Checkin.Stats.CONTENT_URI, id), null);
+                        return -1;
+                    } else {
+                        if (countAdd != null) cursor.updateInt(1, count);
+                        if (sumAdd != null) cursor.updateDouble(2, sum);
+                        cursor.commitUpdates();
+                        return id;
+                    }
+                }
+            } finally {
+                // Always clean up the cursor.
+                if (cursor != null) cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Add an entry into the crashes table.
+     * @param values to insert
+     * @return the row ID of the modified entry
+     */
+    private long insertCrash(ContentValues values) {
+        try {
+            int crashSize = values.getAsString(Checkin.Crashes.DATA).length();
+            if (crashSize > CRASH_SIZE) {
+                // The crash is too big.  Don't report it, but do log a stat.
+                Checkin.updateStats(getContext().getContentResolver(),
+                        Checkin.Stats.Tag.CRASHES_TRUNCATED, 1, 0.0);
+                throw new IllegalArgumentException("Too big: " + crashSize);
+            }
+
+            // Count the number of crashes reported, even if they roll over.
+            Checkin.updateStats(getContext().getContentResolver(),
+                    Checkin.Stats.Tag.CRASHES_REPORTED, 1, 0.0);
+
+            // Trim the crashes database, if needed.
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            db.execSQL("DELETE FROM " +
+                    Checkin.Crashes.TABLE_NAME + " WHERE " +
+                    Checkin.Crashes._ID + " IN (SELECT " +
+                    Checkin.Crashes._ID + " FROM " +
+                    Checkin.Crashes.TABLE_NAME + " ORDER BY " +
+                    Checkin.Crashes._ID + " DESC LIMIT -1 OFFSET " +
+                    (CRASH_LIMIT - 1) + ")");
+
+            return db.insert(Checkin.Crashes.TABLE_NAME, null, values);
+        } catch (Throwable t) {
+            // To avoid an infinite crash-reporting loop, swallow the error.
+            Log.e("CheckinProvider", "Error inserting crash: " + t);
+            return -1;
+        }
+    }
+
+    // TODO: optimize bulkInsert, especially for stats?
+
+    @Override
+    public int update(Uri uri, ContentValues values,
+            String where, String[] args) {
+        checkPermissions(uri);
+        if (uri.getPathSegments().size() == 2) {
+            if (where != null && where.length() > 0) {
+                throw new UnsupportedOperationException(
+                        "WHERE clause not supported for update: " + uri);
+            }
+            where = "_id=" + ContentUris.parseId(uri);
+            args = null;
+        } else if (uri.getPathSegments().size() != 1) {
+            throw new IllegalArgumentException("Invalid update URI: " + uri);
+        }
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int count = db.update(uri.getPathSegments().get(0), values, where, args);
+        getContext().getContentResolver().notifyChange(uri, null);
+        return count;
+    }
+
+    @Override
+    public int delete(Uri uri, String where, String[] args) {
+        checkPermissions(uri);
+        if (uri.getPathSegments().size() == 2) {
+            if (where != null && where.length() > 0) {
+                throw new UnsupportedOperationException(
+                        "WHERE clause not supported for delete: " + uri);
+            }
+            where = "_id=" + ContentUris.parseId(uri);
+            args = null;
+        } else if (uri.getPathSegments().size() != 1) {
+            throw new IllegalArgumentException("Invalid delete URI: " + uri);
+        }
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int count = db.delete(uri.getPathSegments().get(0), where, args);
+        getContext().getContentResolver().notifyChange(uri, null);
+        return count;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        if (uri.getPathSegments().size() == 1) {
+            return "vnd.android.cursor.dir/" + uri.getPathSegments().get(0);
+        } else if (uri.getPathSegments().size() == 2) {
+            return "vnd.android.cursor.item/" + uri.getPathSegments().get(0);
+        } else {
+            throw new IllegalArgumentException("Invalid URI: " + uri);
+        }
+    }
+
+    /**
+     * Make sure the caller has permission to the database.
+     * @param uri the caller is requesting access to
+     * @throws SecurityException if the caller is forbidden.
+     */
+    private void checkPermissions(Uri uri) {
+        if (uri.getPathSegments().size() < 1) {
+            throw new IllegalArgumentException("Invalid query URI: " + uri);
+        }
+
+        String table = uri.getPathSegments().get(0);
+        if (table.equals(Checkin.Properties.TABLE_NAME) &&
+            getContext().checkCallingOrSelfPermission(PROPERTIES_PERMISSION) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Cannot access checkin properties");
+        }
+    }
+}
diff --git a/core/java/android/server/checkin/FallbackCheckinService.java b/core/java/android/server/checkin/FallbackCheckinService.java
new file mode 100644
index 0000000..b450913
--- /dev/null
+++ b/core/java/android/server/checkin/FallbackCheckinService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.checkin;
+
+import android.os.ICheckinService;
+import android.os.RemoteException;
+import android.os.IParentalControlCallback;
+import com.google.android.net.ParentalControlState;
+
+/**
+ * @hide
+ */
+public final class FallbackCheckinService extends ICheckinService.Stub {
+    public FallbackCheckinService() {
+    }
+
+    public void reportCrashSync(byte[] crashData) throws RemoteException {
+    }
+
+    public void reportCrashAsync(byte[] crashData) throws RemoteException {
+    }
+
+    public void masterClear() throws RemoteException {
+    }
+
+    public void getParentalControlState(IParentalControlCallback p) throws RemoteException {
+        ParentalControlState state = new ParentalControlState();
+        state.isEnabled = false;
+        p.onResult(state);
+    }
+}
diff --git a/core/java/android/server/checkin/package.html b/core/java/android/server/checkin/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/core/java/android/server/checkin/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>
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..5b9942e
--- /dev/null
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+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 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!
+    
+    /**
+     * 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);
+            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);
+            }
+            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() {
+        // decompose searchMode attribute
+        // TODO How do I reconcile these hardcoded values with the flag bits defined in
+        // in attrs.xml?  e.g. android.R.id.filterMode = 0x010200a4 instead of just "1"
+    /*  mFilterMode = (0 != (mSearchMode & 1));  */
+    /*  mQuickStart = (0 != (mSearchMode & 2));  */
+        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 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 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) {
+        mLabelId = in.readInt();
+        mSearchActivity = ComponentName.readFromParcel(in);
+        mHintId = in.readInt();
+        mSearchMode = in.readInt();
+        mIconId = in.readInt();
+        mSearchButtonText = 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();
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mLabelId);
+        mSearchActivity.writeToParcel(dest, flags);
+        dest.writeInt(mHintId);
+        dest.writeInt(mSearchMode);
+        dest.writeInt(mIconId);
+        dest.writeInt(mSearchButtonText);
+        
+        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);
+    }
+}
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/recognition/AbstractEmbeddedGrammarListener.java b/core/java/android/speech/recognition/AbstractEmbeddedGrammarListener.java
new file mode 100644
index 0000000..c25a7e3
--- /dev/null
+++ b/core/java/android/speech/recognition/AbstractEmbeddedGrammarListener.java
@@ -0,0 +1,51 @@
+/*---------------------------------------------------------------------------*
+ *  AbstractEmbeddedGrammarListener.java                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * An EmbeddedGrammarListener whose methods are empty. This class exists as
+ * convenience for creating listener objects.
+ */
+public abstract class AbstractEmbeddedGrammarListener implements EmbeddedGrammarListener
+{
+  public void onCompileAllSlots()
+  {
+  }
+
+  public void onError(Exception e)
+  {
+  }
+
+  public void onLoaded()
+  {
+  }
+
+  public void onResetAllSlots()
+  {
+  }
+
+  public void onSaved(String path)
+  {
+  }
+
+  public void onUnloaded()
+  {
+  }
+}
diff --git a/core/java/android/speech/recognition/AbstractGrammarListener.java b/core/java/android/speech/recognition/AbstractGrammarListener.java
new file mode 100644
index 0000000..fe62290
--- /dev/null
+++ b/core/java/android/speech/recognition/AbstractGrammarListener.java
@@ -0,0 +1,39 @@
+/*---------------------------------------------------------------------------*
+ *  AbstractGrammarListener.java                                             *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * A GrammarListener whose methods are empty. This class exists as convenience
+ * for creating listener objects.
+ */
+public abstract class AbstractGrammarListener implements GrammarListener
+{
+  public void onError(Exception e)
+  {
+  }
+
+  public void onLoaded()
+  {
+  }
+
+  public void onUnloaded()
+  {
+  }
+}
diff --git a/core/java/android/speech/recognition/AbstractRecognizerListener.java b/core/java/android/speech/recognition/AbstractRecognizerListener.java
new file mode 100644
index 0000000..ee2b8d1
--- /dev/null
+++ b/core/java/android/speech/recognition/AbstractRecognizerListener.java
@@ -0,0 +1,83 @@
+/*---------------------------------------------------------------------------*
+ *  AbstractRecognizerListener.java                                          *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * A RecognizerListener whose methods are empty. This class exists as
+ * convenience for creating listener objects.
+ */
+public abstract class AbstractRecognizerListener implements RecognizerListener
+{
+  public void onBeginningOfSpeech()
+  {
+  }
+
+  public void onEndOfSpeech()
+  {
+  }
+
+  public void onRecognitionSuccess(RecognitionResult result)
+  {
+  }
+
+  public void onRecognitionFailure(FailureReason reason)
+  {
+  }
+
+  public void onError(Exception e)
+  {
+  }
+
+  public void onParametersGetError(Vector<String> parameters, Exception e)
+  {
+  }
+
+  public void onParametersSetError(Hashtable<String, String> parameters,
+    Exception e)
+  {
+  }
+
+  public void onParametersGet(Hashtable<String, String> parameters)
+  {
+  }
+
+  public void onParametersSet(Hashtable<String, String> parameters)
+  {
+  }
+
+  public void onStartOfSpeechTimeout()
+  {
+  }
+
+  public void onAcousticStateReset()
+  {
+  }
+
+  public void onStarted()
+  {
+  }
+
+  public void onStopped()
+  {
+  }
+}
diff --git a/core/java/android/speech/recognition/AbstractSrecGrammarListener.java b/core/java/android/speech/recognition/AbstractSrecGrammarListener.java
new file mode 100644
index 0000000..e62e4ba
--- /dev/null
+++ b/core/java/android/speech/recognition/AbstractSrecGrammarListener.java
@@ -0,0 +1,59 @@
+/*---------------------------------------------------------------------------*
+ *  AbstractSrecGrammarListener.java                                         *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * An SrecGrammarListener whose methods are empty. This class exists as
+ * convenience for creating listener objects.
+ */
+public abstract class AbstractSrecGrammarListener implements SrecGrammarListener
+{
+  public void onCompileAllSlots()
+  {
+  }
+
+  public void onError(Exception e)
+  {
+  }
+
+  public void onLoaded()
+  {
+  }
+
+  public void onResetAllSlots()
+  {
+  }
+
+  public void onSaved(String path)
+  {
+  }
+
+  public void onUnloaded()
+  {
+  }
+  
+  public void onAddItemList()
+  {
+  }
+    
+  public void onAddItemListFailure(int index, Exception e)
+  {
+  }
+}
diff --git a/core/java/android/speech/recognition/AudioAlreadyInUseException.java b/core/java/android/speech/recognition/AudioAlreadyInUseException.java
new file mode 100644
index 0000000..90698a7
--- /dev/null
+++ b/core/java/android/speech/recognition/AudioAlreadyInUseException.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------*
+ *  AudioAlreadyInUseException.java                                          *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Thrown when an AudioStream is passed into a component when another component
+ * is already using it.
+ */
+public class AudioAlreadyInUseException extends IllegalArgumentException
+{
+  private static final long serialVersionUID = 0L;
+
+  public AudioAlreadyInUseException(String msg)
+  {
+    super(msg);
+  }
+}
diff --git a/core/java/android/speech/recognition/AudioDriverErrorException.java b/core/java/android/speech/recognition/AudioDriverErrorException.java
new file mode 100644
index 0000000..a755e7f
--- /dev/null
+++ b/core/java/android/speech/recognition/AudioDriverErrorException.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------*
+ *  AudioDriverErrorException.java                                           *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Thrown if an error occurs in the audio driver.
+ */
+public class AudioDriverErrorException extends Exception
+{
+  private static final long serialVersionUID = 0L;
+
+  public AudioDriverErrorException(String msg)
+  {
+    super(msg);
+  }
+}
diff --git a/core/java/android/speech/recognition/AudioSource.java b/core/java/android/speech/recognition/AudioSource.java
new file mode 100644
index 0000000..c4cd802
--- /dev/null
+++ b/core/java/android/speech/recognition/AudioSource.java
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------------------------*
+ *  AudioSource.java                                                         *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Generates audio data.
+ */
+public interface AudioSource
+{
+  /**
+   * Returns an object that contains the audio samples. This object
+   * is passed to other components that consumes it, such a Recognizer
+   * or a DeviceSpeaker.
+   *
+   * @return an AudioStream instance
+   */
+  AudioStream createAudio();
+
+  /**
+   * Tells the audio source to start collecting audio samples.
+   */
+  void start();
+
+  /**
+   * Tells the audio source to stop collecting audio samples.
+   */
+  void stop();
+}
diff --git a/core/java/android/speech/recognition/AudioSourceListener.java b/core/java/android/speech/recognition/AudioSourceListener.java
new file mode 100644
index 0000000..42e8ebe
--- /dev/null
+++ b/core/java/android/speech/recognition/AudioSourceListener.java
@@ -0,0 +1,44 @@
+/*---------------------------------------------------------------------------*
+ *  AudioSourceListener.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for Microphone events.
+ */
+public interface AudioSourceListener
+{
+  /**
+   * Invoked after the microphone starts recording.
+   */
+  void onStarted();
+
+  /**
+   * Invoked after the microphone stops recording.
+   */
+  void onStopped();
+
+  /**
+   * Invoked when an unexpected error occurs. This is normally followed by
+   * onStopped() if the component shuts down successfully.
+   *
+   * @param e the cause of the failure
+   */
+  void onError(Exception e);
+}
diff --git a/core/java/android/speech/recognition/AudioStream.java b/core/java/android/speech/recognition/AudioStream.java
new file mode 100644
index 0000000..36afe21
--- /dev/null
+++ b/core/java/android/speech/recognition/AudioStream.java
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------*
+ *  AudioStream.java                                                         *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Stream used to read audio data.
+ */
+public interface AudioStream
+{
+  /**
+   * Releases resources associated with the object.
+   *
+   * @deprecated this method is deprecated and has no replacement. It will be 
+   * removed in a future release of the API.
+   */
+  @Deprecated
+  void dispose();
+}
diff --git a/core/java/android/speech/recognition/Codec.java b/core/java/android/speech/recognition/Codec.java
new file mode 100644
index 0000000..18d9e15
--- /dev/null
+++ b/core/java/android/speech/recognition/Codec.java
@@ -0,0 +1,126 @@
+/*---------------------------------------------------------------------------*
+ *  Codec.java                                                               *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Audio formats.
+ */
+public abstract class Codec
+{
+  /**
+   * PCM, 16 bits, 8KHz.
+   */
+  public static final Codec PCM_16BIT_8K = new Codec("PCM/16bit/8KHz")
+  {
+    @Override
+    public byte getBitsPerSample()
+    {
+      return 16;
+    }
+
+    @Override
+    public int getSampleRate()
+    {
+      return 8000;
+    }
+  };
+  /**
+   * PCM, 16 bits, 11KHz.
+   */
+  public static final Codec PCM_16BIT_11K = new Codec("PCM/16bit/11KHz")
+  {
+    @Override
+    public byte getBitsPerSample()
+    {
+      return 16;
+    }
+
+    @Override
+    public int getSampleRate()
+    {
+      return 11025;
+    }
+  };
+  /**
+   * PCM, 16 bits, 22KHz.
+   */
+  public static final Codec PCM_16BIT_22K = new Codec("PCM/16bit/22KHz")
+  {
+    @Override
+    public byte getBitsPerSample()
+    {
+      return 16;
+    }
+
+    @Override
+    public int getSampleRate()
+    {
+      return 22050;
+    }
+  };
+  /**
+   * ULAW, 8 bits, 8KHz.
+   */
+  public static final Codec ULAW_8BIT_8K = new Codec("ULAW/8bit/8KHz")
+  {
+    @Override
+    public byte getBitsPerSample()
+    {
+      return 8;
+    }
+
+    @Override
+    public int getSampleRate()
+    {
+      return 8000;
+    }
+  };
+  private final String message;
+
+  /**
+   * Creates a new Codec.
+   *
+   * @param message the message to associate with the codec
+   */
+  private Codec(String message)
+  {
+    this.message = message;
+  }
+
+  @Override
+  public String toString()
+  {
+    return message;
+  }
+
+  /**
+   * Returns the codec sample-rate.
+   * 
+   * @return the codec sample-rate
+   */
+  public abstract int getSampleRate();
+
+  /**
+   * Returns the codec bitrate.
+   * 
+   * @return the codec bitrate
+   */
+  public abstract byte getBitsPerSample();
+}
diff --git a/core/java/android/speech/recognition/DeviceSpeaker.java b/core/java/android/speech/recognition/DeviceSpeaker.java
new file mode 100644
index 0000000..bd18687
--- /dev/null
+++ b/core/java/android/speech/recognition/DeviceSpeaker.java
@@ -0,0 +1,77 @@
+/*---------------------------------------------------------------------------*
+ *  DeviceSpeaker.java                                                       *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.DeviceSpeakerImpl;
+
+/**
+ * A device for transforming electric signals into audible sound, most
+ * frequently used to reproduce speech and music.
+ */
+public abstract class DeviceSpeaker
+{
+  private static DeviceSpeaker instance;
+
+  /**
+   * Returns the device speaker instance.
+   *
+   * @return an instance of a DeviceSpeaker class.
+   */
+  public static DeviceSpeaker getInstance()
+  {
+    instance = DeviceSpeakerImpl.getInstance();
+    return instance;
+  }
+
+  /**
+   * Starts the audio playback.
+   *
+   * @param source the audio to play
+   * @throws IllegalStateException if the component is already started
+   * @throws IllegalArgumentException if source audio is null, in-use by 
+   * another component or is empty.
+   *
+   */
+  public abstract void start(AudioStream source) throws IllegalStateException,
+    IllegalArgumentException;
+
+  /**
+   * Stops audio playback.
+   */
+  public abstract void stop();
+
+  /**
+   * Sets the playback codec. This must be called before start() is called.
+   *
+   * @param playbackCodec the codec to use for the playback operation.
+   * @throws IllegalStateException if the component is already stopped
+   * @throws IllegalArgumentException if the specified codec is not supported
+   */
+  public abstract void setCodec(Codec playbackCodec) throws IllegalStateException,
+    IllegalArgumentException;
+
+  /**
+   * Sets the microphone listener.
+   *
+   * @param listener the device speaker listener.
+   * @throws IllegalStateException if the component is started
+   */
+  public abstract void setListener(DeviceSpeakerListener listener) throws IllegalStateException;
+}
diff --git a/core/java/android/speech/recognition/DeviceSpeakerListener.java b/core/java/android/speech/recognition/DeviceSpeakerListener.java
new file mode 100644
index 0000000..e2baa2e
--- /dev/null
+++ b/core/java/android/speech/recognition/DeviceSpeakerListener.java
@@ -0,0 +1,44 @@
+/*---------------------------------------------------------------------------*
+ *  DeviceSpeakerListener.java                                               *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for DeviceSpeaker events.
+ */
+public interface DeviceSpeakerListener
+{
+  /**
+   * Invoked after playback begins.
+   */
+  void onStarted();
+
+  /**
+   * Invoked after playback terminates.
+   */
+  void onStopped();
+
+  /**
+   * Invoked when an unexpected error occurs. This is normally followed by
+   * onStopped() if the component shuts down successfully.
+   *
+   * @param e the cause of the failure
+   */
+  void onError(Exception e);
+}
diff --git a/core/java/android/speech/recognition/EmbeddedGrammar.java b/core/java/android/speech/recognition/EmbeddedGrammar.java
new file mode 100644
index 0000000..c6f037b
--- /dev/null
+++ b/core/java/android/speech/recognition/EmbeddedGrammar.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------*
+ *  EmbeddedGrammar.java                                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Grammar on an embedded recognizer.
+ */
+public interface EmbeddedGrammar extends Grammar
+{
+  /**
+   * Compiles items that were added to any of the grammar slots.
+   */
+  void compileAllSlots();
+
+  /**
+   * Removes all words added to all slots.
+   */
+  void resetAllSlots();
+
+  /**
+   * Saves the compiled grammar.
+   *
+   * @param url the url to save the grammar to
+   */
+  void save(String url);
+}
diff --git a/core/java/android/speech/recognition/EmbeddedGrammarListener.java b/core/java/android/speech/recognition/EmbeddedGrammarListener.java
new file mode 100644
index 0000000..5b8c1a4
--- /dev/null
+++ b/core/java/android/speech/recognition/EmbeddedGrammarListener.java
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------*
+ *  EmbeddedGrammarListener.java                                             *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for EmbeddedGrammar events.
+ */
+public interface EmbeddedGrammarListener extends GrammarListener
+{
+  /**
+   * Invoked after the grammar is saved.
+   *
+   * @param path the path the grammar was saved to
+   */
+  void onSaved(String path);
+
+  /**
+   * Invoked when a grammar operation fails.
+   *
+   * @param e the cause of the failure.<br/>
+   * {@link GrammarOverflowException} if the grammar slot is full and no
+   * further items may be added to it.<br/>
+   * {@link java.lang.UnsupportedOperationException} if different words with
+   * the same pronunciation are added.<br/>
+   * {@link java.lang.IllegalStateException} if reseting or compiling the
+   * slots fails.<br/>
+   * {@link java.io.IOException} if the grammar could not be loaded or
+   * saved.</p>
+   */
+  void onError(Exception e);
+
+  /**
+   * Invokes after all grammar slots have been compiled.
+   */
+  void onCompileAllSlots();
+
+  /**
+   * Invokes after all grammar slots have been reset.
+   */
+  void onResetAllSlots();
+}
diff --git a/core/java/android/speech/recognition/EmbeddedRecognizer.java b/core/java/android/speech/recognition/EmbeddedRecognizer.java
new file mode 100644
index 0000000..cd79edc
--- /dev/null
+++ b/core/java/android/speech/recognition/EmbeddedRecognizer.java
@@ -0,0 +1,66 @@
+/*---------------------------------------------------------------------------*
+ *  EmbeddedRecognizer.java                                                  *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import android.speech.recognition.impl.EmbeddedRecognizerImpl;
+
+/**
+ * Embedded recognizer.
+ */
+public abstract class EmbeddedRecognizer implements Recognizer
+{
+  private static EmbeddedRecognizer instance;
+
+  /**
+   * Returns the embedded recognizer.
+   *
+   * @return the embedded recognizer
+   */
+  public static EmbeddedRecognizer getInstance()
+  {
+    instance = EmbeddedRecognizerImpl.getInstance();
+    return instance;
+  }
+
+  /**
+   * Configures the recognizer.
+   *
+   * @param config recognizer configuration file
+   * @throws IllegalArgumentException if config is null or an empty string
+   * @throws FileNotFoundException if the specified file could not be found
+   * @throws IOException if the specified file could not be opened
+   * @throws UnsatisfiedLinkError if the recognizer plugin could not be loaded
+   * @throws ClassNotFoundException if the recognizer plugin could not be found
+   */
+  public abstract void configure(String config) throws IllegalArgumentException,
+    FileNotFoundException, IOException, UnsatisfiedLinkError,
+    ClassNotFoundException;
+
+   /**
+   * The recognition accuracy improves over time as the recognizer adapts to
+   * the surrounding environment. This method enables developers to reset the
+   * adaptation when the environment is known to have changed.
+   *
+   * @throws IllegalArgumentException if recognizer instance is null
+   */
+  public abstract void resetAcousticState() throws IllegalArgumentException;
+}
diff --git a/core/java/android/speech/recognition/Grammar.java b/core/java/android/speech/recognition/Grammar.java
new file mode 100644
index 0000000..9f1b624
--- /dev/null
+++ b/core/java/android/speech/recognition/Grammar.java
@@ -0,0 +1,43 @@
+/*---------------------------------------------------------------------------*
+ *  Grammar.java                                                             *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Speech recognition grammar.
+ */
+public interface Grammar {
+    /**
+     * Load the grammar sets the grammar state to active, indicating that can be used in a recognition process.
+     * Multiple grammars can be loaded, but only one at a time can be used by the recognizer.
+     *
+     */
+    void load();
+    
+    /**
+     * Unload the grammar sets the grammar state to inactive (inactive grammars can not be used as a parameter of a recognition).
+     */
+    void unload();
+    
+    /**
+     * (Optional operation) Releases resources associated with the object. The
+     * grammar may not be used past this point.
+     */
+    void dispose();
+}
diff --git a/core/java/android/speech/recognition/GrammarErrorException.java b/core/java/android/speech/recognition/GrammarErrorException.java
new file mode 100644
index 0000000..6070758
--- /dev/null
+++ b/core/java/android/speech/recognition/GrammarErrorException.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------*
+ *  GrammarErrorException.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Thrown if an error occurs in the audio driver.
+ */
+public class GrammarErrorException extends Exception
+{
+  private static final long serialVersionUID = 0L;
+
+  public GrammarErrorException(String msg)
+  {
+    super(msg);
+  }
+}
diff --git a/core/java/android/speech/recognition/GrammarListener.java b/core/java/android/speech/recognition/GrammarListener.java
new file mode 100644
index 0000000..871cbcb
--- /dev/null
+++ b/core/java/android/speech/recognition/GrammarListener.java
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------------------------*
+ *  GrammarListener.java                                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for Grammar events.
+ */
+public interface GrammarListener
+{
+  /**
+   * Invoked after the Grammar is loaded.
+   */
+  void onLoaded();
+
+  /**
+   * Invoked after the Grammar is unloaded.
+   */
+  void onUnloaded();
+
+  /**
+   * Invoked when a grammar operation fails.
+   *
+   * @param e the cause of the failure.<br/>
+   * {@link java.io.IOException} if the grammar could not be loaded or
+   * saved.</p>
+   */
+  void onError(Exception e);
+}
diff --git a/core/java/android/speech/recognition/GrammarOverflowException.java b/core/java/android/speech/recognition/GrammarOverflowException.java
new file mode 100644
index 0000000..227820b
--- /dev/null
+++ b/core/java/android/speech/recognition/GrammarOverflowException.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------*
+ *  GrammarOverflowException.java                                            *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Thrown if a SlotItem is added into a grammar slot that is filled to capacity.
+ */
+public class GrammarOverflowException extends Exception
+{
+  private static final long serialVersionUID = 0L;
+
+  public GrammarOverflowException(String message)
+  {
+    super(message);
+  }
+}
diff --git a/core/java/android/speech/recognition/InvalidURLException.java b/core/java/android/speech/recognition/InvalidURLException.java
new file mode 100644
index 0000000..fec9411
--- /dev/null
+++ b/core/java/android/speech/recognition/InvalidURLException.java
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------*
+ *  InvalidURLException.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ */
+public class InvalidURLException extends Exception {
+    
+    private static final long serialVersionUID = 0L;
+      
+    /** Creates a new instance of InvalidURLException */
+    public InvalidURLException(String msg) 
+    {
+        super(msg);
+    }
+
+}
diff --git a/core/java/android/speech/recognition/Logger.java b/core/java/android/speech/recognition/Logger.java
new file mode 100644
index 0000000..8a09cb3
--- /dev/null
+++ b/core/java/android/speech/recognition/Logger.java
@@ -0,0 +1,127 @@
+/*---------------------------------------------------------------------------*
+ *  Logger.java                                                              *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.LoggerImpl;
+
+/**
+ * Logs debugging information.
+ */
+public abstract class Logger
+{
+  /**
+   * Logging level
+   */
+  public static class LogLevel
+  {
+    /**
+     * Does not log.
+     */
+    public static LogLevel LEVEL_NONE = new LogLevel("Do not log");
+    /**
+     * Logs fatal issues. This level only logs ERROR.
+     */
+    public static LogLevel LEVEL_ERROR = new LogLevel("log  UAPI_ERROR logs");
+    /**
+     * Logs non-fatal issues. This level also logs ERROR.
+     */
+    public static LogLevel LEVEL_WARN =
+      new LogLevel("log  UAPI_ERROR, UAPI_WARN logs");
+    /**
+     * Logs debugging information, such as the values of variables. This level also logs ERROR, WARN.
+     */
+    public static LogLevel LEVEL_INFO =
+      new LogLevel("log  UAPI_ERROR, UAPI_WARN, UAPI_INFO logs");
+    /**
+     * Logs when loggers are created or destroyed. This level also logs INFO, WARN, ERROR.
+     */
+    public static LogLevel LEVEL_TRACE =
+      new LogLevel("log UAPI_ERROR, UAPI_WARN, UAPI_INFO, UAPI_TRACE logs");
+    private String message;
+
+    /**
+     * Creates a new LogLevel.
+     *
+     * @param message the message associated with the LogLevel.
+     */
+    private LogLevel(String message)
+    {
+      this.message = message;
+    }
+
+    @Override
+    public String toString()
+    {
+      return message;
+    }
+  }
+
+  /**
+   * Returns the singleton instance.
+   *
+   * @return the singleton instance
+   */
+  public static Logger getInstance()
+  {
+    return LoggerImpl.getInstance();
+  }
+
+  /**
+   * Sets the logging level.
+   *
+   * @param level the logging level
+   */
+  public abstract void setLoggingLevel(LogLevel level);
+
+  /**
+   * Sets the log path.
+   *
+   * @param path the path of the log file
+   */
+  public abstract void setPath(String path);
+
+  /**
+   * Logs an error message.
+   *
+   * @param message the message to log
+   */
+  public abstract void error(String message);
+
+  /**
+   * Logs a warning message.
+   *
+   * @param message the message to log
+   */
+  public abstract void warn(String message);
+
+  /**
+   * Logs an informational message.
+   *
+   * @param message the message to log
+   */
+  public abstract void info(String message);
+
+  /**
+   * Logs a method tracing message.
+   *
+   * @param message the message to log
+   */
+  public abstract void trace(String message);
+}
diff --git a/core/java/android/speech/recognition/MediaFileReader.java b/core/java/android/speech/recognition/MediaFileReader.java
new file mode 100644
index 0000000..216511f
--- /dev/null
+++ b/core/java/android/speech/recognition/MediaFileReader.java
@@ -0,0 +1,90 @@
+/*---------------------------------------------------------------------------*
+ *  MediaFileReader.java                                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.MediaFileReaderImpl;
+
+/**
+ * Reads audio from a file.
+ */
+public abstract class MediaFileReader implements AudioSource
+{
+  /**
+   * Reading mode
+   */
+  public static class Mode
+  {
+    /**
+     * Read the file in "real time".
+     */
+    public static Mode REAL_TIME = new Mode("real-time");
+    /**
+     * Read the file all at once.
+     */
+    public static Mode ALL_AT_ONCE = new Mode("all at once");
+    private String message;
+
+    /**
+     * Creates a new Mode.
+     *
+     * @param message the message associated with the reading mode.
+     */
+    private Mode(String message)
+    {
+      this.message = message;
+    }
+  }
+
+  /**
+   * Creates a new MediaFileReader to read audio samples from a file.
+   *
+   * @param filename the name of the file to read from Note: The file MUST be of type Microsoft WAVE RIFF
+   * format (PCM 16 bits 8000 Hz or PCM 16 bits 11025 Hz).
+   * @param listener listens for MediaFileReader events
+   * @return a new MediaFileReader
+   * @throws IllegalArgumentException if filename is null or is an empty string. Or if offset > file length. Or if codec is null or invalid
+   */
+  public static MediaFileReader create(String filename, AudioSourceListener listener) throws IllegalArgumentException
+  {
+    return new MediaFileReaderImpl(filename, listener);
+  }
+
+  /**
+   * Sets the reading mode.
+   *
+   * @param mode the reading mode
+   */
+  public abstract void setMode(Mode mode);
+
+  /**
+   * Creates an audio source.
+   */
+  public abstract AudioStream createAudio();
+
+  /**
+   * Starts collecting audio samples.
+   */
+  public abstract void start();
+
+  /**
+   * Stops collecting audio samples.
+   */
+  public abstract void stop();
+}
diff --git a/core/java/android/speech/recognition/MediaFileReaderListener.java b/core/java/android/speech/recognition/MediaFileReaderListener.java
new file mode 100644
index 0000000..f76e65f
--- /dev/null
+++ b/core/java/android/speech/recognition/MediaFileReaderListener.java
@@ -0,0 +1,29 @@
+/*---------------------------------------------------------------------------*
+ *  MediaFileReaderListener.java                                             *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.AudioSourceListener;
+
+/**
+ * Listens for MediaFileReader events.
+ */
+public interface MediaFileReaderListener extends AudioSourceListener
+{
+}
diff --git a/core/java/android/speech/recognition/MediaFileWriter.java b/core/java/android/speech/recognition/MediaFileWriter.java
new file mode 100644
index 0000000..b2d627c
--- /dev/null
+++ b/core/java/android/speech/recognition/MediaFileWriter.java
@@ -0,0 +1,49 @@
+/*---------------------------------------------------------------------------*
+ *  MediaFileWriter.java                                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.MediaFileWriterImpl;
+
+/**
+ * Writes audio to a file.
+ */
+public abstract class MediaFileWriter
+{
+  /**
+   * Creates a new MediaFileWriter to write audio samples into a file.
+   *
+   * @param listener listens for MediaFileWriter events
+   * @return a new MediaFileWriter
+   */
+  public static MediaFileWriter create(MediaFileWriterListener listener)
+  {
+    return new MediaFileWriterImpl(listener);
+  }
+
+  /**
+   * Saves audio to a file.
+   *
+   * @param source the audio stream to write
+   * @param filename the file to write to
+   * @throws IllegalArgumentException if source is null, in-use by another 
+   * component or contains no data. Or if filename is null or is empty.
+   */
+  public abstract void save(AudioStream source, String filename) throws IllegalArgumentException;
+}
diff --git a/core/java/android/speech/recognition/MediaFileWriterListener.java b/core/java/android/speech/recognition/MediaFileWriterListener.java
new file mode 100644
index 0000000..e2104c8
--- /dev/null
+++ b/core/java/android/speech/recognition/MediaFileWriterListener.java
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------*
+ *  MediaFileWriterListener.java                                             *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for MediaFileWriter events.
+ */
+public interface MediaFileWriterListener
+{
+  /**
+   * Invoked after the save() operation terminates
+   */
+  void onStopped();
+
+  /**
+   * Invoked when an unexpected error occurs. This is normally followed by
+   * onStopped() if the component shuts down successfully.
+   *
+   * @param e the cause of the failure.<br/>
+   * {@link java.io.IOException} if an error occured opening or writing to the file
+   */
+  void onError(Exception e);
+}
diff --git a/core/java/android/speech/recognition/Microphone.java b/core/java/android/speech/recognition/Microphone.java
new file mode 100644
index 0000000..1b713f5
--- /dev/null
+++ b/core/java/android/speech/recognition/Microphone.java
@@ -0,0 +1,76 @@
+/*---------------------------------------------------------------------------*
+ *  Microphone.java                                                          *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.MicrophoneImpl;
+
+/**
+ * Records live audio.
+ */
+public abstract class Microphone implements AudioSource
+{
+  private static Microphone instance;
+
+  /**
+   * Returns the microphone instance
+   *
+   * @return an instance of a Microphone class.
+   */
+  public static Microphone getInstance()
+  {
+    instance = MicrophoneImpl.getInstance();
+    return instance;
+  }
+
+  /**
+   * Sets the recording codec. This must be called before start() is called.
+   *
+   * @param recordingCodec the codec in which the samples will be recorded.
+   * @throws IllegalStateException if Microphone is started
+   * @throws IllegalArgumentException if codec is not supported
+   */
+  public abstract void setCodec(Codec recordingCodec) throws IllegalStateException,
+    IllegalArgumentException;
+
+  /**
+   * Sets the microphone listener.
+   *
+   * @param listener the microphone listener.
+   * @throws IllegalStateException if Microphone is started
+   */
+  public abstract void setListener(AudioSourceListener listener) throws IllegalStateException;
+
+  /**
+   * Creates an audio source
+   */
+  public abstract AudioStream createAudio();
+
+  /**
+   * Start recording audio.
+   *
+   * @throws IllegalStateException if Microphone is already started
+   */
+  public abstract void start() throws IllegalStateException;
+
+  /**
+   * Stops recording audio.
+   */
+  public abstract void stop();
+}
diff --git a/core/java/android/speech/recognition/MicrophoneListener.java b/core/java/android/speech/recognition/MicrophoneListener.java
new file mode 100644
index 0000000..f43eff9
--- /dev/null
+++ b/core/java/android/speech/recognition/MicrophoneListener.java
@@ -0,0 +1,29 @@
+/*---------------------------------------------------------------------------*
+ *  MicrophoneListener.java                                                  *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.AudioSourceListener;
+
+/**
+ * Listens for Microphone events.
+ */
+public interface MicrophoneListener extends AudioSourceListener
+{
+}
diff --git a/core/java/android/speech/recognition/NBestRecognitionResult.java b/core/java/android/speech/recognition/NBestRecognitionResult.java
new file mode 100644
index 0000000..e679c19
--- /dev/null
+++ b/core/java/android/speech/recognition/NBestRecognitionResult.java
@@ -0,0 +1,113 @@
+/*---------------------------------------------------------------------------*
+ *  NBestRecognitionResult.java                                              *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import java.util.Enumeration;
+
+/**
+ * N-Best recognition results. Entries are sorted in decreasing order according
+ * to their probability, from the most probable result to the least probable
+ * result.
+ */
+public interface NBestRecognitionResult extends RecognitionResult
+{
+  /**
+   * Recognition result entry
+   */
+  public static interface Entry
+  {
+    /**
+     * Returns the semantic meaning of a recognition result (i.e.&nbsp;the application-specific value
+     * associated with what the user said). In an example where a person's name is mapped
+     * to a phone-number, the phone-number is the semantic meaning.
+     *
+     * @return the semantic meaning of a recognition result.
+     * @throws IllegalStateException if the object has been disposed
+     */
+    String getSemanticMeaning() throws IllegalStateException;
+
+    /**
+     * The confidence score of a recognition result. Values range from 0 to 100
+     * (inclusive).
+     *
+     * @return the confidence score of a recognition result.
+     * @throws IllegalStateException if the object has been disposed
+     */
+    byte getConfidenceScore() throws IllegalStateException;
+
+    /**
+     * Returns the literal meaning of a recognition result (i.e.&nbsp;literally
+     * what the user said). In an example where a person's name is mapped to a 
+     * phone-number, the person's name is the literal meaning.
+     *
+     * @return the literal meaning of a recognition result.
+     * @throws IllegalStateException if the object has been disposed
+     */
+    String getLiteralMeaning() throws IllegalStateException;
+    
+    /**
+     * Returns the value associated with the specified key.
+     *
+     * @param key the key to look up
+     * @return the associated value or null if this entry does not contain
+     * any mapping for the key
+     */
+    String get(String key);
+    
+    /**
+     * Returns an enumeration of the keys in this Entry.
+     *
+     * @return an enumeration of the keys in this Entry.
+     */
+    Enumeration keys();
+  }
+
+  /**
+   * Returns the number of entries in the n-best list.
+   *
+   * @return the number of entries in the n-best list
+   */
+  int getSize();
+
+  /**
+   * Returns the n-best entry that contains key-value pairs associated with the
+   * recognition result.
+   *
+   * @param index the index of the n-best entry
+   * @return null if all active GrammarConfiguration.grammarToMeaning() return
+   * null
+   * @throws ArrayIndexOutOfBoundsException if index is greater than size of
+   * entries
+   */
+  Entry getEntry(int index) throws ArrayIndexOutOfBoundsException;
+
+  /**
+   * Creates a new VoicetagItem if the last recognition was an enrollment
+   * operation.
+   *
+   * @param VoicetagId string voicetag unique id value. 
+   * @param listener listens for Voicetag events
+   * @return the resulting VoicetagItem
+   * @throws IllegalArgumentException if VoicetagId is null or an empty string.
+   * @throws IllegalStateException if the last recognition was not an
+   * enrollment operation
+   */
+  VoicetagItem createVoicetagItem(String VoicetagId, VoicetagItemListener listener) throws IllegalArgumentException,IllegalStateException;
+}
diff --git a/core/java/android/speech/recognition/ParameterErrorException.java b/core/java/android/speech/recognition/ParameterErrorException.java
new file mode 100644
index 0000000..042ed31
--- /dev/null
+++ b/core/java/android/speech/recognition/ParameterErrorException.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------*
+ *  ParameterErrorException.java                                             *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Thrown if an error occurs in the audio driver.
+ */
+public class ParameterErrorException extends Exception
+{
+  private static final long serialVersionUID = 0L;
+
+  public ParameterErrorException(String msg)
+  {
+    super(msg);
+  }
+}
diff --git a/core/java/android/speech/recognition/ParametersListener.java b/core/java/android/speech/recognition/ParametersListener.java
new file mode 100644
index 0000000..bdb551e
--- /dev/null
+++ b/core/java/android/speech/recognition/ParametersListener.java
@@ -0,0 +1,63 @@
+/*---------------------------------------------------------------------------*
+ *  ParametersListener.java                                                  *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Listens for parameter events.
+ */
+public interface ParametersListener
+{
+  /**
+   * Invoked if retrieving parameters has failed.
+   *
+   * @param parameters the parameters that could not be retrieved
+   * @param e the failure reason
+   */
+  void onParametersGetError(Vector<String> parameters, Exception e);
+
+  /**
+   * Invoked if setting parameters has failed.
+   *
+   * @param parameters the parameters that could not be set
+   * @param e the failure reason
+   */
+  void onParametersSetError(Hashtable<String, String> parameters, Exception e);
+
+  /**
+   * This method is called when the parameters specified in setParameters have
+   * successfully been set. This method is guaranteed to be invoked after
+   * onParametersSetError, even if count==0.
+   *
+   * @param parameters the set parameters
+   */
+  void onParametersSet(Hashtable<String, String> parameters);
+
+  /**
+   * This method is called when the parameters specified in getParameters have
+   * successfully been retrieved. This method is guaranteed to be invoked after
+   * onParametersGetError, even if count==0.
+   *
+   * @param parameters the retrieved parameters
+   */
+  void onParametersGet(Hashtable<String, String> parameters);
+}
diff --git a/core/java/android/speech/recognition/ParseErrorException.java b/core/java/android/speech/recognition/ParseErrorException.java
new file mode 100644
index 0000000..2288a90
--- /dev/null
+++ b/core/java/android/speech/recognition/ParseErrorException.java
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------*
+ *  ParseErrorException.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Thrown if an error occurs in the audio driver.
+ */
+public class ParseErrorException extends Exception
+{
+  private static final long serialVersionUID = 0L;
+
+  public ParseErrorException(String msg)
+  {
+    super(msg);
+  }
+}
diff --git a/core/java/android/speech/recognition/RecognitionResult.java b/core/java/android/speech/recognition/RecognitionResult.java
new file mode 100644
index 0000000..cbbc938
--- /dev/null
+++ b/core/java/android/speech/recognition/RecognitionResult.java
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------*
+ *  RecognitionResult.java                                                   *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Recognition result interface.
+ */
+public interface RecognitionResult
+{
+}
diff --git a/core/java/android/speech/recognition/Recognizer.java b/core/java/android/speech/recognition/Recognizer.java
new file mode 100644
index 0000000..ab7f8f4
--- /dev/null
+++ b/core/java/android/speech/recognition/Recognizer.java
@@ -0,0 +1,102 @@
+/*---------------------------------------------------------------------------*
+ *  Recognizer.java                                                          *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Speech recognizer interface.
+ */
+public interface Recognizer
+{
+ /**
+   * Sets the recognizer event listener.
+   *
+   * @param listener listens for recognizer events
+   */
+  void setListener(RecognizerListener listener);
+
+  /**
+   * Creates an embedded grammar.
+   *
+   * @param value value of that grammarType. Could be a URL or an inline grammar.
+   * @return a grammar
+   * @throws IllegalArgumentException if value is null or listener is not of type
+   * GrammarListener.
+   */
+  Grammar createGrammar(String value, GrammarListener listener) throws IllegalArgumentException;
+  
+  /**
+   * Begins speech recognition.
+   *
+   * @param audio the audio stream to recognizer
+   * @param grammars a collection of grammar sets to recognize against
+   * @see #recognize(AudioStream, Grammar)
+   * @throws IllegalStateException if any of the grammars are not loaded
+   * @throws IllegalArgumentException if audio is null, in-use by another
+   * component or empty. Or if grammars is null or grammars count is less than
+   * one. Or if the audio codec differs from recognizer codec.
+   * @throws UnsupportedOperationException if the recognizer does not support
+   * the number of grammars specified.
+   */
+  void recognize(AudioStream audio,
+    Vector<Grammar> grammars) throws IllegalStateException,
+    IllegalArgumentException, UnsupportedOperationException;
+
+  /**
+   * This convenience method is equivalent to invoking
+   * recognize(audio, grammars) with a single grammar.
+   *
+   * @param audio the audio to recognizer
+   * @param grammar a grammar to recognize against
+   * @see #recognize(AudioStream, Vector)
+   * @throws IllegalStateException if grammar is not loaded
+   * @throws IllegalArgumentException if audio is null, in-use by another
+   * component or is empty. Or if grammar is null or if the audio codec differs
+   * from the recognizer codec.
+   */
+  void recognize(AudioStream audio, Grammar grammar) throws IllegalStateException,
+    IllegalArgumentException;
+
+  /**
+   * Terminates a recognition if one is in-progress.
+   * This must not be called until the recognize method
+   * returns; otherwise the result is not defined.
+   *
+   * @see RecognizerListener#onStopped
+   */
+  void stop();
+
+  /**
+   * Sets the values of recognition parameters.
+   *
+   * @param parameters the parameter key-value pairs to set
+   */
+  void setParameters(Hashtable<String, String> parameters);
+
+  /**
+   * Retrieves the values of recognition parameters.
+   *
+   * @param parameters the names of the parameters to retrieve
+   */
+  void getParameters(Vector<String> parameters);
+
+}
diff --git a/core/java/android/speech/recognition/RecognizerListener.java b/core/java/android/speech/recognition/RecognizerListener.java
new file mode 100644
index 0000000..d7bbda9
--- /dev/null
+++ b/core/java/android/speech/recognition/RecognizerListener.java
@@ -0,0 +1,142 @@
+/*---------------------------------------------------------------------------*
+ *  RecognizerListener.java                                                  *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for recognizer events.
+ */
+public interface RecognizerListener extends ParametersListener
+{
+  /**
+   * Recognition failure.
+   */
+  public static class FailureReason
+  {
+    /**
+     * The audio did not generate any results.
+     */
+    public static FailureReason NO_MATCH =
+      new FailureReason("The audio did not generate any results");
+    /**
+     * Beginning of speech occured too soon.
+     */
+    public static FailureReason SPOKE_TOO_SOON =
+      new FailureReason("Beginning of speech occurred too soon");
+    /**
+     * A timeout occured before the beginning of speech.
+     */
+    public static FailureReason BEGINNING_OF_SPEECH_TIMEOUT =
+      new FailureReason("A timeout occurred before the beginning of " + "speech");
+    /**
+     * A timeout occured before the recognition could complete.
+     */
+    public static FailureReason RECOGNITION_TIMEOUT =
+      new FailureReason("A timeout occurred before the recognition " +
+      "could complete");
+    /**
+     * The recognizer encountered more audio than was acceptable according to
+     * its configuration.
+     */
+    public static FailureReason TOO_MUCH_SPEECH =
+      new FailureReason("The " +
+      "recognizer encountered more audio than was acceptable according to " +
+      "its configuration");
+
+    public static FailureReason UNKNOWN =
+      new FailureReason("unknown failure reason");
+
+    private final String message;
+
+    private FailureReason(String message)
+    {
+      this.message = message;
+    }
+
+    @Override
+    public String toString()
+    {
+      return message;
+    }
+  }
+
+  /**
+   * Invoked after recognition begins.
+   */
+  void onStarted();
+
+  /**
+   * Invoked if the recognizer detects the beginning of speech.
+   */
+  void onBeginningOfSpeech();
+
+  /**
+   * Invoked if the recognizer detects the end of speech.
+   */
+  void onEndOfSpeech();
+
+  /**
+   * Invoked if the recognizer does not detect speech within the configured
+   * timeout period.
+   */
+  void onStartOfSpeechTimeout();
+
+  /**
+   * Invoked when the recognizer acoustic state is reset.
+   *
+   * @see android.speech.recognition.EmbeddedRecognizer#resetAcousticState()
+   */
+  void onAcousticStateReset();
+
+  /**
+   * Invoked when a recognition result is generated.
+   *
+   * @param result the recognition result. The result object can not be
+   * used outside of the scope of the onRecognitionSuccess() callback method.
+   * To be able to do so, copy it's contents to an user-defined object.<BR>
+   * An example of this object could be a vector of string arrays; where the
+   * vector represents a list of recognition result entries and each entry
+   * is an array of strings to hold the entry's values (the semantic
+   * meaning, confidence score and literal meaning).
+   */
+  void onRecognitionSuccess(RecognitionResult result);
+
+  /**
+   * Invoked when a recognition failure occurs.
+   *
+   * @param reason the failure reason
+   */
+  void onRecognitionFailure(FailureReason reason);
+
+  /**
+   * Invoked when an unexpected error occurs. This is normally followed by
+   * onStopped() if the component shuts down successfully.
+   *
+   * @param e the cause of the failure
+   */
+  void onError(Exception e);
+
+  /**
+   * Invoked when the recognizer stops (due to normal termination or an error).
+   *
+   * Invoking stop() on a recognizer that is already stopped will not result
+   * in a onStopped() event.
+   */
+  void onStopped();
+}
diff --git a/core/java/android/speech/recognition/SlotItem.java b/core/java/android/speech/recognition/SlotItem.java
new file mode 100644
index 0000000..3abd27a
--- /dev/null
+++ b/core/java/android/speech/recognition/SlotItem.java
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------*
+ *  SlotItem.java                                                            *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Item that may be inserted into an embedded grammar slot.
+ */
+public interface SlotItem
+{
+}
diff --git a/core/java/android/speech/recognition/SrecGrammar.java b/core/java/android/speech/recognition/SrecGrammar.java
new file mode 100644
index 0000000..c591e05
--- /dev/null
+++ b/core/java/android/speech/recognition/SrecGrammar.java
@@ -0,0 +1,81 @@
+/*---------------------------------------------------------------------------*
+ *  SrecGrammar.java                                                         *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+import java.util.Vector;
+
+/**
+ * Grammar on an SREC recognizer.
+ */
+public interface SrecGrammar extends EmbeddedGrammar
+{
+  /**
+  * SrecGrammar Item
+  */
+  public class Item
+  {
+      public SlotItem _item;
+      public int _weight;
+      public String _semanticMeaning;
+      
+       /**
+       * Creates a grammar item.
+       *
+       * @param item the Slotitem. 
+       * @param weight the weight of the item. Smaller values are more likely to get recognized.  This should be >= 0.
+       * @param semanticMeaning the value that will be returned if this item is recognized.
+       * @throws IllegalArgumentException if item or semanticMeaning are null; if semanticMeaning is empty."
+       */
+       public Item(SlotItem item, int weight, String semanticMeaning)
+             throws IllegalArgumentException
+      {
+         if (item == null)
+            throw new IllegalArgumentException("Item(): item can't be null.");
+         if (semanticMeaning == null || semanticMeaning.length()==0)
+            throw new IllegalArgumentException("Item(): semanticMeaning is null or empty.");
+          _item = item;
+          _weight = weight;
+          _semanticMeaning = semanticMeaning;
+          
+      }
+  }
+  
+  /**
+   * Adds an item to a slot.
+   *
+   * @param slotName the name of the slot
+   * @param item the item to add to the slot.
+   * @param weight the weight of the item. Smaller values are more likely to get recognized.  This should be >= 0.
+   * @param semanticMeaning the value that will be returned if this item is recognized.
+   * @throws IllegalArgumentException if slotName, item or semanticMeaning are null; if semanticMeaning is not of the format "V=&#039;Jen_Parker&#039;"
+   */
+  public void addItem(String slotName, SlotItem item, int weight,
+    String semanticMeaning) throws IllegalArgumentException;
+  
+  /**
+   * Add a list of item to a slot.
+   *
+   * @param slotName the name of the slot
+   * @param items the vector of SrecGrammar.Item to add to the slot.
+   * @throws IllegalArgumentException if slotName,items are null or any element in the items(_item, _semanticMeaning) is null; if any semanticMeaning of the list is not of the format "key=&#039;value&#039"
+   */
+  public void addItemList(String slotName, Vector<Item> items)
+          throws IllegalArgumentException;
+  
+}
diff --git a/core/java/android/speech/recognition/SrecGrammarListener.java b/core/java/android/speech/recognition/SrecGrammarListener.java
new file mode 100644
index 0000000..e1f7d3f
--- /dev/null
+++ b/core/java/android/speech/recognition/SrecGrammarListener.java
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------*
+ *  SrecGrammarListener.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for SrecGrammar events.
+ */
+public interface SrecGrammarListener extends EmbeddedGrammarListener {
+    
+   /**
+   * Invokes after all items of the list have been added.
+   */
+   void onAddItemList();
+   
+   /**
+   * Invoked when adding a SlotItem from a list fails. 
+   * This callback will be trigger for each element in the list that fails to be
+   * add in the slot, unless there is a grammar fail operation, which will be
+   * reported in the onError callback.
+   * @param index of the list that could not be added to the slot
+   * @param e the cause of the failure.
+   */
+   void onAddItemListFailure(int index, Exception e);
+
+    
+   /**
+   * Invoked when a grammar related operation fails.
+   *
+   * @param e the cause of the failure.<br/>
+   * {@link GrammarOverflowException} if the grammar slot is full and no
+   * further items may be added to it.<br/>
+   * {@link java.lang.UnsupportedOperationException} if different words with
+   * the same pronunciation are added.<br/>
+   * {@link java.lang.IllegalStateException} if reseting or compiling the
+   * slots fails.<br/>
+   * {@link java.io.IOException} if the grammar could not be loaded or
+   * saved.</p>
+   */
+   void onError(Exception e);
+  
+}
diff --git a/core/java/android/speech/recognition/VoicetagItem.java b/core/java/android/speech/recognition/VoicetagItem.java
new file mode 100644
index 0000000..0b89639
--- /dev/null
+++ b/core/java/android/speech/recognition/VoicetagItem.java
@@ -0,0 +1,82 @@
+/*---------------------------------------------------------------------------*
+ *  VoicetagItem.java                                                        *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.VoicetagItemImpl;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+/**
+ * Voicetag that may be inserted into an embedded grammar slot.
+ */
+public abstract class VoicetagItem implements SlotItem
+{
+   /**
+   * Creates a VoicetagItem from a file
+   *
+   * @param filename filename for Voicetag
+   * @param listener listens for Voicetag events
+   * @return the resulting VoicetagItem
+   * @throws IllegalArgumentException if filename is null or an empty string.
+   * @throws FileNotFoundException if the specified filename could not be found
+   * @throws IOException if the specified filename could not be opened
+   */
+  public static VoicetagItem create(String filename, VoicetagItemListener listener) throws IllegalArgumentException,FileNotFoundException,IOException
+  {
+        return VoicetagItemImpl.create(filename,listener);
+  }
+  /**
+   * Returns the audio used to construct the VoicetagItem.
+   * The audio is in PCM format and is start-pointed and end-pointed. The audio
+   * is only generated if the enableGetWaveform recognition parameter
+   * is set prior to recognition.
+   *
+   * @throws IllegalStateException if the recognition parameter 'enableGetWaveform' is not set
+   * @return the audio used to construct the VoicetagItem.
+   */
+  public abstract byte[] getAudio() throws IllegalStateException;
+
+  /**
+   * Sets the audio used to construct the Voicetag. The
+   * audio is in PCM format and is start-pointed and end-pointed. The audio is
+   * only generated if the enableGetWaveform recognition parameter is set
+   * prior to recognition.
+   *
+   * @param waveform the endpointed waveform
+   * @throws IllegalArgumentException if waveform is null or empty.
+   * @throws IllegalStateException if the recognition parameter 'enableGetWaveform' is not set
+   */
+  public abstract void setAudio(byte[] waveform) throws IllegalArgumentException,IllegalStateException;
+ 
+   /**
+   * Save the Voicetag Item.
+   *
+   * @param path where the Voicetag will be saved. We strongly recommend to set the filename with the same value of the VoicetagId.
+   * @throws IllegalArgumentException if path is null or an empty string.
+   */
+   public abstract void save(String path) throws IllegalArgumentException,IllegalStateException;
+  
+   /**
+   * Load a Voicetag Item.
+   *
+   * @throws IllegalStateException if voicetag has not been created from a file.
+   */
+   public abstract void load() throws IllegalStateException;
+
+}
diff --git a/core/java/android/speech/recognition/VoicetagItemListener.java b/core/java/android/speech/recognition/VoicetagItemListener.java
new file mode 100644
index 0000000..610d1c7
--- /dev/null
+++ b/core/java/android/speech/recognition/VoicetagItemListener.java
@@ -0,0 +1,49 @@
+/*---------------------------------------------------------------------------*
+ *  VoicetagItemListener.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+/**
+ * Listens for VoicetagItem events.
+ */
+public interface VoicetagItemListener
+{
+   /**
+   * Invoked after the Voicetag is saved.
+   *
+   * @param path the path the Voicetag was saved to
+   */
+  void onSaved(String path);
+  
+   /**
+   * Invoked after the Voicetag is loaded.
+   */
+  void onLoaded();
+  
+  /**
+   * Invoked when a grammar operation fails.
+   *
+   * @param e the cause of the failure.<br/>
+   * {@link java.io.IOException} if the Voicetag could not be loaded or
+   * saved.</p>
+   * {@link java.io.FileNotFoundException} if the specified file could not be found
+   */
+  void onError(Exception e);
+  
+}
diff --git a/core/java/android/speech/recognition/WordItem.java b/core/java/android/speech/recognition/WordItem.java
new file mode 100644
index 0000000..5c21c98
--- /dev/null
+++ b/core/java/android/speech/recognition/WordItem.java
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------*
+ *  WordItem.java                                                            *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition;
+
+import android.speech.recognition.impl.WordItemImpl;
+
+/**
+ * Word that may be inserted into an embedded grammar slot.
+ */
+public abstract class WordItem implements SlotItem
+{
+  /**
+   * Creates a new WordItem.
+   *
+   * @param word the word to insert
+   * @param pronunciations the pronunciations to associated with the item. If the list is
+   * is empty (example:new String[0]) the recognizer will attempt to guess the pronunciations.
+   * @return the WordItem
+   * @throws IllegalArgumentException if word is null or if pronunciations is
+   * null or pronunciations contains an element equal to null or empty string.
+   */
+  public static WordItem valueOf(String word, String[] pronunciations) throws IllegalArgumentException
+  {
+    return WordItemImpl.valueOf(word, pronunciations);
+  }
+
+  /**
+   * Creates a new WordItem.
+   *
+   * @param word the word to insert
+   * @param pronunciation the pronunciation to associate with the item. If it
+   * is null the recognizer will attempt to guess the pronunciations.
+   * @return the WordItem
+   * @throws IllegalArgumentException if word is null or if pronunciation is
+   * an empty string
+   */
+  public static WordItem valueOf(String word, String pronunciation) throws IllegalArgumentException
+  {
+    return WordItemImpl.valueOf(word, pronunciation);
+  }
+}
diff --git a/core/java/android/speech/recognition/impl/AudioStreamImpl.java b/core/java/android/speech/recognition/impl/AudioStreamImpl.java
new file mode 100644
index 0000000..730e2d9
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/AudioStreamImpl.java
@@ -0,0 +1,84 @@
+/*---------------------------------------------------------------------------*
+ *  AudioStreamImpl.java                                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.AudioStream;
+
+/**
+ */
+public class AudioStreamImpl implements AudioStream, Runnable
+{
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new AudioStreamImpl.
+   *
+   * @param nativeObj a reference to the native object
+   */
+  public AudioStreamImpl(long nativeObj)
+  {
+     nativeObject = nativeObj;
+  }
+
+  public synchronized void run()
+  {
+    dispose();
+  }
+
+  public long getNativeObject() { 
+     synchronized (AudioStreamImpl.class)
+     {
+        return nativeObject;
+     }
+  }
+  
+  /**
+   * Releases the native resources associated with the object.
+   */
+  @SuppressWarnings("deprecation")
+  public void dispose()
+  {
+    synchronized (AudioStreamImpl.class)
+    {
+        if (nativeObject != 0)
+        {
+            deleteNativeObject(nativeObject);
+            nativeObject = 0;
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  /**
+   * Deletes a native object.
+   *
+   * @param nativeObject pointer to the native object
+   */
+  private native void deleteNativeObject(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java b/core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java
new file mode 100644
index 0000000..5d72110
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java
@@ -0,0 +1,164 @@
+/*---------------------------------------------------------------------------*
+ *  DeviceSpeakerImpl.java                                                   *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.AudioStream;
+import android.speech.recognition.Codec;
+import android.speech.recognition.DeviceSpeaker;
+import android.speech.recognition.DeviceSpeakerListener;
+
+/**
+ */
+public class DeviceSpeakerImpl extends DeviceSpeaker implements Runnable
+{
+  private static DeviceSpeakerImpl instance;
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+  private DeviceSpeakerListener locallistener;
+
+  /**
+   * Private constructor
+   */
+  private DeviceSpeakerImpl()
+  {
+    System system = System.getInstance();
+    nativeObject = initNativeObject();
+    if (nativeObject != 0)
+      system.register(this);
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  /**
+   * Returns the singleton instance.
+   *
+   * @return the singleton instance
+   */
+  public static DeviceSpeakerImpl getInstance()
+  {
+     synchronized (DeviceSpeakerImpl.class)
+     {
+        if (instance == null)
+            instance = new DeviceSpeakerImpl();
+        return instance;
+     }
+  }
+
+  /**
+   * Start audio playback.
+   *
+   * @param source the audio to play
+   */
+  public void start(AudioStream source)
+  {
+     synchronized (DeviceSpeakerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        AudioStreamImpl src = (AudioStreamImpl)source;
+        startProxy(nativeObject,src.getNativeObject());
+        src = null;
+     }
+  }
+
+  /**
+   * Stops audio playback.
+   */
+  public void stop()
+  {
+     synchronized (DeviceSpeakerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        stopProxy(nativeObject);
+     }
+  }
+
+  /**
+   * Set the playback codec. This must be called before start is called.
+   * @param playbackCodec the codec to use for the playback operation.
+   */
+  public void setCodec(Codec playbackCodec)
+  {
+     synchronized (DeviceSpeakerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        setCodecProxy(nativeObject,playbackCodec);
+     }
+  }
+
+  /**
+   * set the microphone listener.
+   * @param listener the device speaker listener.
+   */
+  public void setListener(DeviceSpeakerListener listener)
+  {
+     synchronized (DeviceSpeakerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        locallistener = listener;
+        setListenerProxy(nativeObject,listener);
+     }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+     synchronized (DeviceSpeakerImpl.class)
+     {
+        if (nativeObject != 0)
+        {
+            deleteNativeObject(nativeObject);
+            nativeObject = 0;
+            instance = null;
+            locallistener = null;
+            System.getInstance().unregister(this);
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  private native long initNativeObject();
+
+  private native void startProxy(long nativeObject, long audioNativeObject);
+
+  private native void stopProxy(long nativeObject);
+
+  private native void setCodecProxy(long nativeObject,Codec playbackCodec);
+
+  private native void setListenerProxy(long nativeObject,DeviceSpeakerListener listener);
+
+  private native void deleteNativeObject(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java b/core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java
new file mode 100644
index 0000000..0b88cb2
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java
@@ -0,0 +1,73 @@
+/*---------------------------------------------------------------------------*
+ *  EmbeddedGrammarImpl.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.EmbeddedGrammar;
+
+/**
+ */
+public class EmbeddedGrammarImpl extends GrammarImpl implements EmbeddedGrammar
+{
+  /**
+   * Creates a new EmbeddedGrammarImpl.
+   *
+   * @param nativeObject a reference to the native object
+   */
+  public EmbeddedGrammarImpl(long nativeObject)
+  {
+    super(nativeObject);
+  }
+
+  public void compileAllSlots()
+  {
+     synchronized (GrammarImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        compileAllSlotsProxy(nativeObject);
+     }
+  }
+
+  public void resetAllSlots()
+  {
+     synchronized (GrammarImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        resetAllSlotsProxy(nativeObject);
+     }
+  }
+
+  public void save(String url)
+  {
+     synchronized (GrammarImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        saveProxy(nativeObject, url.toString());
+     }
+  }
+
+  private native void compileAllSlotsProxy(long nativeObject);
+
+  private native void resetAllSlotsProxy(long nativeObject);
+
+  private native void saveProxy(long nativeObject, String url);
+}
diff --git a/core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java b/core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java
new file mode 100644
index 0000000..f04bfe4
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java
@@ -0,0 +1,246 @@
+/*---------------------------------------------------------------------------*
+ *  EmbeddedRecognizerImpl.java                                              *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+import android.speech.recognition.EmbeddedRecognizer;
+import android.speech.recognition.Grammar;
+import android.speech.recognition.AudioStream;
+import android.speech.recognition.Grammar;
+import android.speech.recognition.RecognizerListener;
+import android.speech.recognition.GrammarListener;
+
+/**
+ */
+public class EmbeddedRecognizerImpl extends EmbeddedRecognizer implements Runnable
+{
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+  /**
+   * The singleton instance.
+   */
+  private static EmbeddedRecognizerImpl instance;
+
+  /**
+   * Creates a new instance.
+   */
+  EmbeddedRecognizerImpl()
+  {
+    System system = System.getInstance();
+    nativeObject = getInstanceProxy();
+    if (nativeObject != 0)
+      system.register(this);
+  }
+
+  /**
+   * Returns the singleton instance.
+   *
+   * @return the singleton instance
+   */
+  public synchronized static EmbeddedRecognizerImpl getInstance()
+  {
+     synchronized (EmbeddedRecognizerImpl.class)
+     {
+        if (instance == null)
+            instance = new EmbeddedRecognizerImpl();
+        return instance;
+     }
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+     synchronized (EmbeddedRecognizerImpl.class)
+     {
+        if (instance != null)
+        {
+            deleteNativeObject(nativeObject);
+            nativeObject = 0;
+            instance = null;
+            System.getInstance().unregister(this);
+        }
+     }
+  }
+
+  public void configure(String config) throws IllegalArgumentException,
+    FileNotFoundException, IOException, UnsatisfiedLinkError,
+    ClassNotFoundException
+  {
+     synchronized (EmbeddedRecognizerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        if (config == null)
+            throw new IllegalArgumentException("Configuration Is Null.");
+        configureProxy(nativeObject,config);
+     }
+  }
+
+  public void setListener(RecognizerListener listener)
+  {
+     synchronized (EmbeddedRecognizerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        setListenerProxy(nativeObject,listener);
+     }
+  }
+
+   public Grammar createGrammar(String value, GrammarListener listener) 
+        throws IllegalArgumentException
+  {
+     synchronized (EmbeddedRecognizerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        long nativeGrammar = createEmbeddedGrammarProxy(nativeObject,value.toString(), listener);
+        return new SrecGrammarImpl(nativeGrammar);
+     }
+  }
+
+  public void resetAcousticState()
+  {
+    synchronized (EmbeddedRecognizerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        resetAcousticStateProxy(nativeObject);
+    }
+  }
+
+  public void recognize(AudioStream audio,
+    Vector<Grammar> grammars)
+  {
+    synchronized (EmbeddedRecognizerImpl.class)
+    {
+        if (nativeObject == 0)
+         throw new IllegalStateException("Object was destroyed.");
+
+        if (audio == null)
+          throw new IllegalArgumentException("AudioStream cannot be null.");
+
+        if (grammars == null || grammars.isEmpty() == true)
+          throw new IllegalArgumentException("Grammars are null or empty.");
+        int grammarCount = grammars.size();
+
+        long[] nativeGrammars = new long[grammarCount];
+
+        for (int i = 0; i < grammarCount; ++i)
+          nativeGrammars[i] = ((GrammarImpl) grammars.get(i)).getNativeObject();
+
+        recognizeProxy(nativeObject,((AudioStreamImpl)audio).getNativeObject(), nativeGrammars);
+    }
+  }
+
+  public void recognize(AudioStream audio,
+    Grammar grammar)
+  {
+    synchronized (EmbeddedRecognizerImpl.class)
+    {
+        if (nativeObject == 0)
+         throw new IllegalStateException("Object was destroyed.");
+    }
+    Vector<Grammar> grammars = new Vector<Grammar>();
+    grammars.add(grammar);
+    recognize(audio, grammars);
+  }
+
+  public  void stop()
+  {
+    synchronized (EmbeddedRecognizerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        stopProxy(nativeObject);
+    }
+  }
+
+  public void setParameters(Hashtable<String, String> params)
+  {
+    synchronized (EmbeddedRecognizerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        setParametersProxy(nativeObject,params);
+    }
+  }
+
+  public void getParameters(Vector<String> params)
+  {
+    synchronized (EmbeddedRecognizerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        getParametersProxy(nativeObject,params);
+    }
+  }
+
+  /**
+   * Returns the native EmbeddedRecognizer.
+   *
+   * @return a reference to the native object
+   */
+  private native long getInstanceProxy();
+
+  /**
+   * Configures the recognizer instance.
+   *
+   * @param config the recognizer configuration file
+   */
+  private native void configureProxy(long nativeObject, String config) throws IllegalArgumentException,
+    FileNotFoundException, IOException, UnsatisfiedLinkError,
+    ClassNotFoundException;
+
+  /**
+   * Sets the recognizer listener.
+   *
+   * @param listener listens for recognizer events
+   */
+  private native void setListenerProxy(long nativeObject, RecognizerListener listener);
+
+  private native void recognizeProxy(long nativeObject, long audioNativeObject,
+    long[] pGrammars);
+
+  private native long createEmbeddedGrammarProxy(long nativeObject, String url,
+    GrammarListener listener);
+
+  private native void stopProxy(long nativeObject);
+
+  private native void deleteNativeObject(long nativeObject);
+
+  private native void setParametersProxy(long nativeObject, Hashtable<String, String> params);
+
+  private native void getParametersProxy(long nativeObject, Vector<String> params);
+
+  private native void resetAcousticStateProxy(long nativeObject);
+
+}
diff --git a/core/java/android/speech/recognition/impl/EntryImpl.java b/core/java/android/speech/recognition/impl/EntryImpl.java
new file mode 100644
index 0000000..91b2b78
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/EntryImpl.java
@@ -0,0 +1,147 @@
+/*---------------------------------------------------------------------------*
+ *  EntryImpl.java                                                           *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.NBestRecognitionResult;
+import java.util.Enumeration;
+
+/**
+ */
+public class EntryImpl implements NBestRecognitionResult.Entry, Runnable
+{
+  private long nativeObject;
+
+  /**
+   * This implementation is a work-around to solve Q bug with
+   * nested classes.
+   *
+   * @param nativeObject the native NBestRecognitionResult.Entry object
+   */
+  public EntryImpl(long nativeObject)
+  {
+    this.nativeObject = nativeObject;
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  public byte getConfidenceScore() throws IllegalStateException
+  {
+    synchronized (EntryImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        return getConfidenceScoreProxy(nativeObject);
+    }
+  }
+
+  public String getLiteralMeaning() throws IllegalStateException
+  {
+    synchronized (EntryImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        return getLiteralMeaningProxy(nativeObject);
+    }
+  }
+
+  public String getSemanticMeaning() throws IllegalStateException
+  {
+    synchronized (EntryImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        return getSemanticMeaningProxy(nativeObject);
+    }
+  }
+ 
+  public String get(String key)
+  {
+    synchronized (EntryImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        return getProxy(nativeObject,key);
+    }
+  }
+
+  public Enumeration keys()
+  {
+    synchronized (EntryImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+
+          return new Enumeration()
+          {
+            private String[] keys = keysProxy(nativeObject);
+            private int indexOfNextRead = 0;
+
+            public boolean hasMoreElements()
+            {
+              return indexOfNextRead <= keys.length-1;
+            }
+
+            public Object nextElement()
+            {
+              return keys[indexOfNextRead++];
+            }
+          };
+    }
+  }
+
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+    synchronized (EntryImpl.class)
+    {
+        if (nativeObject != 0)
+        {
+            deleteNativeObject(nativeObject);
+            nativeObject = 0;
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  private native void deleteNativeObject(long nativeObject);
+
+  private native String getLiteralMeaningProxy(long nativeObject);
+
+  private native String getSemanticMeaningProxy(long nativeObject);
+
+  private native byte getConfidenceScoreProxy(long nativeObject);
+  
+  private native String getProxy(long nativeObject,String key);
+
+  private native String[] keysProxy(long nativeObject);
+
+}
diff --git a/core/java/android/speech/recognition/impl/GrammarImpl.java b/core/java/android/speech/recognition/impl/GrammarImpl.java
new file mode 100644
index 0000000..563d5d9
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/GrammarImpl.java
@@ -0,0 +1,114 @@
+/*---------------------------------------------------------------------------*
+ *  GrammarImpl.java                                                         *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.Grammar;
+
+/**
+ */
+public class GrammarImpl implements Grammar, Runnable
+{
+  /**
+   * Reference to the native object.
+   */
+  protected long nativeObject;
+
+  /**
+   * Creates a new GrammarImpl.
+   *
+   * @param nativeObj a reference to the native object
+   */
+  public GrammarImpl(long nativeObj)
+  {
+    nativeObject = nativeObj;
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  public long getNativeObject() 
+  { 
+     synchronized (GrammarImpl.class)
+     {
+        return nativeObject;
+     }
+  }
+  
+  /**
+   * Indicates that the grammar will be used in the near future.
+   */
+  public void load()
+  {
+     synchronized (GrammarImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        loadProxy(nativeObject);
+     }
+  }
+
+  /**
+   * The grammar will be removed from use.
+   */
+  public void unload()
+  {
+     synchronized (GrammarImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        unloadProxy(nativeObject);
+     }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  public void dispose()
+  {
+    synchronized (GrammarImpl.class)
+    {
+        if (nativeObject != 0)
+        {
+            deleteNativeObject(nativeObject);
+            nativeObject = 0;
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  /**
+   * Deletes a native object.
+   *
+   * @param nativeObject pointer to the native object
+   */
+  private native void deleteNativeObject(long nativeObject);
+
+  private native void loadProxy(long nativeObject);
+
+  private native void unloadProxy(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/LoggerImpl.java b/core/java/android/speech/recognition/impl/LoggerImpl.java
new file mode 100644
index 0000000..9933c56
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/LoggerImpl.java
@@ -0,0 +1,166 @@
+/*---------------------------------------------------------------------------*
+ *  LoggerImpl.java                                                          *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.Logger;
+
+/**
+ */
+public class LoggerImpl extends Logger implements Runnable
+{
+  private static LoggerImpl instance;
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new instance of LoggerImpl.
+   *
+   * @param function the name of the enclosing function
+   */
+  private LoggerImpl()
+  {
+    System system = System.getInstance();
+    nativeObject = initNativeObject();
+    if (nativeObject!=0)
+         system.register(this);
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  /**
+   * Returns the singleton instance.
+   *
+   * @return the singleton instance
+   */
+  public static LoggerImpl getInstance()
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (instance == null)
+            instance = new LoggerImpl();
+        return instance;
+    }
+  }
+
+  public void setLoggingLevel(LogLevel level)
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        setLoggingLevelProxy(nativeObject,level);
+    }
+  }
+
+  public void setPath(String path)
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        setPathProxy(nativeObject,path);
+    }
+  }
+
+  public void error(String message)
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        errorProxy(nativeObject,message);
+    }
+  }
+
+  public void warn(String message)
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        warnProxy(nativeObject,message);
+    }
+  }
+
+  public  void info(String message)
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        infoProxy(nativeObject,message);
+    }
+  }
+
+  public void trace(String message)
+  {
+     synchronized (LoggerImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        traceProxy(nativeObject,message);
+     }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+    synchronized (LoggerImpl.class)
+    {
+        if (nativeObject!=0) 
+        {
+            deleteNativeObject(nativeObject);
+            System.getInstance().unregister(this);
+        }
+        nativeObject = 0;
+        instance = null;
+    }
+  }
+ 
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  private native long initNativeObject();
+
+  private native void setLoggingLevelProxy(long nativeObject, LogLevel level);
+
+  private native void setPathProxy(long nativeObject, String filename);
+
+  private native void errorProxy(long nativeObject, String message);
+
+  private native void warnProxy(long nativeObject, String message);
+
+  private native void infoProxy(long nativeObject, String message);
+
+  private native void traceProxy(long nativeObject,String message);
+
+  private native void deleteNativeObject(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/MediaFileReaderImpl.java b/core/java/android/speech/recognition/impl/MediaFileReaderImpl.java
new file mode 100644
index 0000000..8ce643d
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/MediaFileReaderImpl.java
@@ -0,0 +1,156 @@
+/*---------------------------------------------------------------------------*
+ *  MediaFileReaderImpl.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.MediaFileReader;
+import android.speech.recognition.AudioStream;
+import android.speech.recognition.Codec;
+import android.speech.recognition.AudioSourceListener;
+
+/**
+ */
+public class MediaFileReaderImpl extends MediaFileReader implements Runnable
+{
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new MediaFileReaderImpl.
+   *
+   * @param filename the name of the file to read from
+   * @param listener listens for MediaFileReader events
+   */
+  public MediaFileReaderImpl(String filename, AudioSourceListener listener)
+  {
+    System system = System.getInstance();
+    nativeObject =
+      createMediaFileReaderProxy(filename, listener);
+    if (nativeObject != 0)
+      system.register(this);
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  /**
+   * Set the reading mode
+   */
+  public void setMode(Mode mode)
+  {
+    synchronized (MediaFileReaderImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        setModeProxy(nativeObject,mode);
+    }
+  }
+
+  /**
+   * Creates an audioStream source
+   */
+  public AudioStream createAudio()
+  {
+    synchronized (MediaFileReaderImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        return new AudioStreamImpl(createAudioProxy(nativeObject));
+    }
+  }
+
+  /**
+   * Tells the audio source to start collecting audio samples.
+   */
+  public void start()
+  {
+     synchronized (MediaFileReaderImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        startProxy(nativeObject);
+     }
+  }
+
+  /**
+   * Stops this source from collecting audio samples.
+   */
+  public void stop()
+  {
+    synchronized (MediaFileReaderImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        stopProxy(nativeObject);
+    }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  public void dispose()
+  {
+    synchronized (MediaFileReaderImpl.class)
+    {
+        if (nativeObject != 0)
+        {
+          deleteNativeObject(nativeObject);
+          nativeObject = 0;
+          System.getInstance().unregister(this);
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  /**
+   * Deletes a native object.
+   *
+   * @param nativeObject pointer to the native object
+   */
+  private native void deleteNativeObject(long nativeObject);
+
+  /**
+   * Creates a native MediaFileReader.
+   *
+   * @param filename the name of the file to read from
+   * @param offset the offset to begin reading from
+   * @param codec the file audio format
+   * @param listener listens for MediaFileReader events
+   * @return a reference to the native object
+   */
+  private native long createMediaFileReaderProxy(String filename, AudioSourceListener listener);
+
+  private native void setModeProxy(long nativeObject,Mode mode);
+
+  private native long createAudioProxy(long nativeObject);
+
+  private native void startProxy(long nativeObject);
+
+  private native void stopProxy(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/MediaFileWriterImpl.java b/core/java/android/speech/recognition/impl/MediaFileWriterImpl.java
new file mode 100644
index 0000000..c4bd836
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/MediaFileWriterImpl.java
@@ -0,0 +1,102 @@
+/*---------------------------------------------------------------------------*
+ *  MediaFileWriterImpl.java                                                 *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.AudioStream;
+import android.speech.recognition.MediaFileWriter;
+import android.speech.recognition.MediaFileWriterListener;
+
+/**
+ */
+public class MediaFileWriterImpl extends MediaFileWriter implements Runnable
+{
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new MediaFileWriterImpl.
+   *
+   * @param listener listens for MediaFileWriter events
+   */
+  public MediaFileWriterImpl(MediaFileWriterListener listener)
+  {
+    System system = System.getInstance();
+    nativeObject = createMediaFileWriterProxy(listener);
+    if (nativeObject != 0)
+      system.register(this);
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  public void save(AudioStream source, String filename)
+  {
+    synchronized (MediaFileWriterImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        saveProxy(nativeObject,((AudioStreamImpl)source).getNativeObject(), filename);
+    }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  public synchronized void dispose()
+  { 
+     synchronized (MediaFileWriterImpl.class)
+     {
+        if (nativeObject != 0)
+        {
+          deleteNativeObject(nativeObject);
+          nativeObject = 0;
+          System.getInstance().unregister(this);
+        }
+     }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  /**
+   * Creates a native MediaFileWriter.
+   *
+   * @param listener listens for MediaFileReader events
+   * @return a reference to the native object
+   */
+  private native long createMediaFileWriterProxy(MediaFileWriterListener listener);
+
+  /**
+   * Deletes a native object.
+   *
+   * @param nativeObject pointer to the native object
+   */
+  private native void deleteNativeObject(long nativeObject);
+
+  private native void saveProxy(long nativeObject, long audioNativeObject, String filename);
+}
diff --git a/core/java/android/speech/recognition/impl/MicrophoneImpl.java b/core/java/android/speech/recognition/impl/MicrophoneImpl.java
new file mode 100644
index 0000000..a915484
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/MicrophoneImpl.java
@@ -0,0 +1,165 @@
+/*---------------------------------------------------------------------------*
+ *  MicrophoneImpl.java                                                      *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.AudioStream;
+import android.speech.recognition.Codec;
+import android.speech.recognition.Microphone;
+import android.speech.recognition.AudioSourceListener;
+
+/**
+ */
+public class MicrophoneImpl extends Microphone implements Runnable
+{
+  private static MicrophoneImpl instance;
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new MicrophoneImpl.
+   *
+   * @param nativeObj a reference to the native object
+   */
+  private MicrophoneImpl()
+  {
+    System system = System.getInstance();
+    nativeObject = initNativeObject();
+    if (nativeObject != 0)
+      system.register(this);
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+  /**
+   * Returns the singleton instance.
+   *
+   * @return the singleton instance
+   */
+  public static MicrophoneImpl getInstance()
+  {
+    synchronized (MicrophoneImpl.class)
+    {
+        if (instance == null)
+            instance = new MicrophoneImpl();
+        return instance;
+    }
+  }
+
+  /**
+   * set the recording codec. This must be called before Start is called.
+   * @param recordingCodec the codec in which the samples will be recorded.
+   */
+  public void setCodec(Codec recordingCodec)
+  {
+    synchronized (MicrophoneImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        setCodecProxy(nativeObject,recordingCodec);
+    }
+  }
+
+  /**
+   * set the microphone listener.
+   * @param listener the microphone listener.
+   */
+  public void setListener(AudioSourceListener listener)
+  {
+    synchronized (MicrophoneImpl.class)
+    {       
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        setListenerProxy(nativeObject,listener);
+    }
+  }
+
+  public AudioStream createAudio()
+  {
+    synchronized (MicrophoneImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        return new AudioStreamImpl(createAudioProxy(nativeObject));
+    }
+  }
+
+  public void start()
+  {
+     synchronized (MicrophoneImpl.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        startProxy(nativeObject);
+     }
+  }
+
+  public void stop()
+  {
+    synchronized (MicrophoneImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        stopProxy(nativeObject);
+    }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+    synchronized (MicrophoneImpl.class)
+    {
+        if (nativeObject != 0)
+        {
+          deleteNativeObject(nativeObject);
+          nativeObject = 0;
+          instance = null;
+          System.getInstance().unregister(this);
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  private native long initNativeObject();
+
+  private native void setCodecProxy(long nativeObject,Codec recordingCodec);
+
+  private native void setListenerProxy(long nativeObject, AudioSourceListener listener);
+
+  private native long createAudioProxy(long nativeObject);
+
+  private native void startProxy(long nativeObject);
+
+  private native void stopProxy(long nativeObject);
+
+  private native void deleteNativeObject(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java b/core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java
new file mode 100644
index 0000000..4d2e00a
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java
@@ -0,0 +1,106 @@
+/*---------------------------------------------------------------------------*
+ *  NBestRecognitionResultImpl.java                                          *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.NBestRecognitionResult;
+import android.speech.recognition.VoicetagItem;
+import android.speech.recognition.VoicetagItemListener;
+/**
+ */
+public class NBestRecognitionResultImpl implements NBestRecognitionResult
+{
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new NBestRecognitionResultImpl.
+   *
+   * @param nativeObject a reference to the native object
+   */
+  public NBestRecognitionResultImpl(long nativeObject)
+  {
+    this.nativeObject = nativeObject;
+  }
+
+  public int getSize()
+  {
+    synchronized (NBestRecognitionResultImpl.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+        return getSizeProxy(nativeObject);
+    }
+  }
+
+  public Entry getEntry(int index)
+  {
+    synchronized (NBestRecognitionResultImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        long nativeEntryObject = getEntryProxy(nativeObject,index);
+        if (nativeEntryObject==0)
+          return null;
+        else
+          return new EntryImpl(nativeEntryObject);
+    }
+  }
+
+  public VoicetagItem createVoicetagItem(String VoicetagId, VoicetagItemListener listener)
+  {
+    synchronized (NBestRecognitionResultImpl.class)
+    {
+        if (nativeObject == 0)
+          throw new IllegalStateException("Object has been disposed");
+        if ((VoicetagId == null) || (VoicetagId.length() == 0))
+          throw new IllegalArgumentException("VoicetagId may not be null or empty string.");
+        return new VoicetagItemImpl(createVoicetagItemProxy(nativeObject,VoicetagId,listener),false);
+     }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+    synchronized (NBestRecognitionResultImpl.class)
+    {
+        nativeObject = 0;
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  /**
+   * Returns a reference to the native VoicetagItem.
+   */
+  private native long createVoicetagItemProxy(long nativeObject, String VoicetagId, VoicetagItemListener listener);
+
+  private native long getEntryProxy(long nativeObject, int index);
+
+  private native int getSizeProxy(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/SrecGrammarImpl.java b/core/java/android/speech/recognition/impl/SrecGrammarImpl.java
new file mode 100644
index 0000000..cb6f4c6
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/SrecGrammarImpl.java
@@ -0,0 +1,120 @@
+/*---------------------------------------------------------------------------*
+ *  SrecGrammarImpl.java                                                     *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.SrecGrammar;
+import android.speech.recognition.SlotItem;
+import android.speech.recognition.VoicetagItem;
+import android.speech.recognition.WordItem;
+
+import java.util.Vector;
+
+/**
+ */
+public class SrecGrammarImpl extends EmbeddedGrammarImpl implements SrecGrammar
+{
+  /**
+   * Creates a new SrecGrammarImpl.
+   *
+   * @param nativeObject the native object
+   */
+  public SrecGrammarImpl(long nativeObject)
+  {
+    super(nativeObject);
+  }
+
+  public void addItem(String slotName, SlotItem item, int weight,
+    String semanticValue)
+  {
+     synchronized (GrammarImpl.class)
+     {
+          if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+
+        if (slotName == null || slotName.length()==0)
+             throw new IllegalArgumentException("addItem() - Slot name is null or empty.");
+        if (item == null)
+             throw new IllegalArgumentException("addItem() - item can't be null.");
+        if (semanticValue == null || semanticValue.length()==0)
+             throw new IllegalArgumentException("addItem() - semanticValue is null or empty.");
+
+        long itemNativeObject = 0;
+        if  (item instanceof VoicetagItem)
+            itemNativeObject = ((VoicetagItemImpl)item).getNativeObject();
+        else if  (item instanceof WordItem)
+            itemNativeObject = ((WordItemImpl)item).getNativeObject();
+        else
+           throw new IllegalArgumentException("SlotItem - should be a WordItem or a VoicetagItem object.");
+
+        addItemProxy(nativeObject, slotName, itemNativeObject, weight, semanticValue);
+     }
+  }
+ 
+  public void addItemList(String slotName, Vector<Item> items)
+  {
+     synchronized (GrammarImpl.class)
+     {
+          if (nativeObject == 0)
+            throw new IllegalStateException("Object has been disposed");
+
+          if (slotName == null || slotName.length()==0)
+                  throw new IllegalArgumentException("addItemList - Slot name is null or empty.");
+          if (items == null || items.isEmpty() == true)
+                throw new IllegalArgumentException("addItemList - Items is null or empty.");
+
+          int itemsCount = items.size();
+
+          long[]     nativeSlots    = new long[itemsCount];
+          int[]      nativeWeights  = new int[itemsCount];
+          String[]   nativeSemantic = new String[itemsCount];
+
+          Item element = null;
+          long itemNativeObject = 0;
+          SlotItem item = null;
+          for (int i = 0; i < itemsCount; ++i)
+          {
+            element = items.get(i);
+
+            item = element._item;
+            if  (item instanceof VoicetagItem)
+                itemNativeObject = ((VoicetagItemImpl)item).getNativeObject();
+            else if  (item instanceof WordItem)
+                itemNativeObject = ((WordItemImpl)item).getNativeObject();
+            else
+            {
+               throw new IllegalArgumentException("SlotItem ["+i+"] - should be a WordItem or a VoicetagItem object.");
+            }
+            nativeSlots[i] = itemNativeObject;
+            nativeWeights[i] = element._weight;
+            nativeSemantic[i]= element._semanticMeaning;
+            itemNativeObject = 0;
+            item = null;
+          }
+          addItemListProxy(nativeObject, slotName,nativeSlots,nativeWeights,nativeSemantic);
+     }
+  }
+  
+  private native void addItemProxy(long nativeObject, String slotName, long item, int weight,
+    String semanticValue);
+  
+  private native void addItemListProxy(long nativeObject, String slotName, long[] items,
+          int[] weights, String[] semanticValues);
+  
+}
diff --git a/core/java/android/speech/recognition/impl/System.java b/core/java/android/speech/recognition/impl/System.java
new file mode 100644
index 0000000..23418fea
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/System.java
@@ -0,0 +1,179 @@
+/*---------------------------------------------------------------------------*
+ *  System.java                                                              *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+
+/**
+ */
+public class System
+{
+  private static boolean libraryLoaded;
+  private static System instance;
+  private static WeakHashMap<Object, WeakReference> registerMap;
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+  private boolean shutdownRequested;
+
+  /**
+   * Creates a new instance of System
+   */
+  private System()
+  {
+    shutdownRequested = false;
+    registerMap =
+      new WeakHashMap<Object, WeakReference>();
+    initLibrary();
+    nativeObject = initNativeObject();
+        Runtime.getRuntime().
+      addShutdownHook(new Thread()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          dispose();
+        }
+        catch (Exception e)
+        {
+          e.printStackTrace();
+        }
+      }
+    });
+
+  }
+
+  /**
+   * Returns the singleton instance.
+   *
+   * @return the singleton instance
+   */
+  public static System getInstance()
+  {
+    synchronized (System.class)
+    {
+      if (instance == null)
+        instance = new System();
+      return instance;
+    }
+  }
+
+  /**
+   * Loads the native library if necessary.
+   */
+  private void initLibrary()
+  {
+    if (!libraryLoaded)
+    {
+      java.lang.System.loadLibrary("UAPI_jni");
+      libraryLoaded = true;
+    }
+  }
+
+  /**
+   * Registers an object for shutdown when System.dispose() is invoked.
+   *
+   * @param r the code to run on shutdown
+   * @throws IllegalStateException if the System is shutting down
+   */
+  public void register(Runnable r) throws IllegalStateException
+  {
+    synchronized (System.class)
+    {
+      if (shutdownRequested)
+        throw new IllegalStateException("System is shutting down");
+      registerMap.put(r,
+        new WeakReference<Runnable>(r));
+    }
+  }
+
+  /**
+   * Registers an object for shutdown when System.dispose() is invoked.
+   *
+   * @param r the code to run on shutdown
+   */
+  public void unregister(Runnable r)
+  {
+    synchronized (System.class)
+    {
+      if (shutdownRequested)
+      {
+        // System.dispose() will end up removing all entries
+        return;
+      }
+      if (r!=null) registerMap.remove(r);
+    }
+  }
+
+  /**
+   * Releases the native resources associated with the object.
+   *
+   * @throws java.util.concurrent.TimeoutException if the operation timeouts
+   * @throws IllegalThreadStateException if a native thread error occurs
+   */
+  public void dispose() throws java.util.concurrent.TimeoutException,
+    IllegalThreadStateException
+  {
+    synchronized (System.class)
+    {
+      if (nativeObject == 0)
+        return;
+      shutdownRequested = true;
+    }
+
+    // Traverse the list of WeakReferences
+    // cast to a Runnable object if the weakrerefence is not null
+    // then call the run method.
+    for (Object o: registerMap.keySet())
+    {
+      WeakReference weakReference = registerMap.get(o);
+      Runnable r = (Runnable) weakReference.get();
+      if (r != null)
+        r.run();
+    }
+    registerMap.clear();
+
+    // Call the native dispose method
+    disposeProxy();
+    synchronized (System.class)
+    {
+      nativeObject = 0;
+      instance = null;
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  public static native String getAPIVersion();
+  
+  private static native long initNativeObject();
+
+  private static native void disposeProxy();
+}
diff --git a/core/java/android/speech/recognition/impl/VoicetagItemImpl.java b/core/java/android/speech/recognition/impl/VoicetagItemImpl.java
new file mode 100644
index 0000000..f9db399
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/VoicetagItemImpl.java
@@ -0,0 +1,206 @@
+/*---------------------------------------------------------------------------*
+ *  VoicetagItemImpl.java                                                    *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.VoicetagItem;
+import android.speech.recognition.VoicetagItemListener;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+/**
+ */
+public class VoicetagItemImpl  extends VoicetagItem implements Runnable
+{
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+  /**
+   * Voicetag has a filename need to be loaded before use it.
+   */
+  private boolean needToBeLoaded;
+  
+  /**
+   * Creates a new VoicetagItemImpl.
+   *
+   * @param nativeObject the pointer to the native object
+   */
+  public VoicetagItemImpl(long nativeObject, boolean fromfile)
+  {
+    this.nativeObject = nativeObject;
+    needToBeLoaded = fromfile;
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+   /**
+   * Creates a VoicetagItem from a file
+   *
+   * @param filename filename for Voicetag
+   * @param listener listens for Voicetag events
+   * @return the resulting VoicetagItem
+   * @throws IllegalArgumentException if filename is null or an empty string.
+   * @throws FileNotFoundException if the specified filename could not be found
+   * @throws IOException if the specified filename could not be opened
+   */
+  public static VoicetagItem create(String filename, VoicetagItemListener listener) throws IllegalArgumentException,FileNotFoundException,IOException
+  {
+      if ((filename == null) || (filename.length() == 0))
+        throw new IllegalArgumentException("Filename may not be null or empty string.");
+       
+      VoicetagItemImpl voicetag = null;
+      long nativeVoicetag = createVoicetagProxy(filename,listener);
+      if (nativeVoicetag!=0)
+      {
+        voicetag = new VoicetagItemImpl(nativeVoicetag,true);
+      }
+      return voicetag;
+  }
+  /**
+   * Returns the audio used to construct the VoicetagItem.
+   */
+  public byte[] getAudio() throws IllegalStateException
+  {
+     synchronized (VoicetagItem.class)
+     {
+        if (nativeObject == 0)
+         throw new IllegalStateException("Object was destroyed.");
+
+        return getAudioProxy(nativeObject);
+     }
+  }
+
+  /**
+   * Sets the audio used to construct the Voicetag.
+   */
+  public void setAudio(byte[] waveform) throws IllegalArgumentException,IllegalStateException
+  {
+    synchronized (VoicetagItem.class)
+    {
+        if (nativeObject == 0)
+         throw new IllegalStateException("Object was destroyed.");
+
+         if ((waveform == null) || (waveform.length == 0))
+            throw new IllegalArgumentException("Waveform may not be null or empty.");
+         setAudioProxy(nativeObject,waveform);
+    }
+  }
+  
+  /**
+  * Save the Voicetag.
+  */
+  public void save(String path) throws IllegalArgumentException
+  {
+    synchronized (VoicetagItem.class)
+    {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        if ((path == null) || (path.length() == 0))
+            throw new IllegalArgumentException("Path may not be null or empty string.");
+        saveVoicetagProxy(nativeObject,path);
+    }
+  }
+  
+  /**
+  * Load a Voicetag.
+  */
+  public void load() throws IllegalStateException
+  {
+     synchronized (VoicetagItem.class)
+     {
+        if (nativeObject == 0)
+            throw new IllegalStateException("Object was destroyed.");
+        if (!needToBeLoaded) 
+           throw new IllegalStateException("This Voicetag was not created from a file, does not need to be loaded.");
+        loadVoicetagProxy(nativeObject);
+     }
+ }
+  
+  public long getNativeObject() 
+  { 
+     synchronized (VoicetagItem.class)
+     {
+      return nativeObject;
+     }
+  }
+  
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+    synchronized (VoicetagItem.class)
+    {
+        if (nativeObject != 0)
+        {
+          deleteNativeObject(nativeObject);
+          nativeObject = 0;
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  
+  private static native long createVoicetagProxy(String filename, VoicetagItemListener listener);
+  /**
+  * (Optional operation) Returns the audio used to construct the Voicetag. The
+  * audio is in PCM format and is start-pointed and end-pointed. The audio is
+  * only generated if the enableGetWaveform recognition parameter is set
+  * prior to recognition.
+  *
+  * @see RecognizerParameters.enableGetWaveform
+  */
+  private native byte[] getAudioProxy(long nativeObject);
+
+  /**
+  * (Optional operation) Sets the audio used to construct the Voicetag. The
+  * audio is in PCM format and is start-pointed and end-pointed. The audio is
+  * only generated if the enableGetWaveform recognition parameter is set
+  * prior to recognition.
+  *
+  * @param waveform the endpointed waveform
+  */
+  private native void setAudioProxy(long nativeObject, byte[] waveform);
+   
+  /**
+  * Save the Voicetag Item.
+  */
+  private native void saveVoicetagProxy(long nativeObject, String path);
+   
+  /**
+  * Load a Voicetag Item.
+  */
+  private native void loadVoicetagProxy(long nativeObject);
+  
+  /**
+   * Deletes a native object.
+   *
+   * @param nativeObject pointer to the native object
+   */
+  private native void deleteNativeObject(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/WordItemImpl.java b/core/java/android/speech/recognition/impl/WordItemImpl.java
new file mode 100644
index 0000000..f0daa34
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/WordItemImpl.java
@@ -0,0 +1,157 @@
+/*---------------------------------------------------------------------------*
+ *  WordItemImpl.java                                                        *
+ *                                                                           *
+ *  Copyright 2007, 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.recognition.impl;
+
+import android.speech.recognition.WordItem;
+
+/**
+ */
+public class WordItemImpl extends WordItem implements Runnable
+{
+  /**
+   * Empty array that gets reused whenever the code requests that the underlying
+   * recognizer guess the pronunciations.
+   */
+  private static final String[] guessPronunciations = new String[0];
+  /**
+   * Reference to the native object.
+   */
+  private long nativeObject;
+
+  /**
+   * Creates a new WordItem.
+   *
+   * @param word the word to insert
+   * @throws IllegalArgumentException if word or pronunciations are null
+   */
+  private WordItemImpl(String word, String[] pronunciations) throws IllegalArgumentException
+  {
+    initNativeObject(word, pronunciations);
+  }
+
+  public void run()
+  {
+    dispose();
+  }
+
+   /**
+   * Creates a new WordItem.
+   *
+   * @param word the word to insert
+   * @param pronunciations the pronunciations to associated with the item. If the list is
+   * is empty (example:new String[0]) the recognizer will attempt to guess the pronunciations.
+   * @return the WordItem
+   * @throws IllegalArgumentException if word is null or if pronunciations is
+   * null or pronunciations contains an element equal to null or empty string.
+   */
+  public static WordItemImpl valueOf(String word, String[] pronunciations)
+    throws IllegalArgumentException
+  {
+    if (word == null)
+      throw new IllegalArgumentException("Word may not be null");
+    else if (pronunciations == null)
+      throw new IllegalArgumentException("Pronunciations may not be null");
+    for (int i = 0, size = pronunciations.length; i < size; ++i)
+    {
+       if (pronunciations[i]==null)
+       {
+           throw new IllegalArgumentException(
+                   "Pronunciations element may not be null");
+       }
+       else
+       {
+           if (pronunciations[i].trim().equals(""))
+             throw new IllegalArgumentException(
+                     "Pronunciations may not contain empty strings");
+       }
+    }
+    return new WordItemImpl(word, pronunciations);
+  }
+
+  /**
+   * Creates a new WordItem.
+   *
+   * @param word the word to insert
+   * @param pronunciation the pronunciation to associate with the item. If it
+   * is null the recognizer will attempt to guess the pronunciations.
+   * @return the WordItem
+   * @throws IllegalArgumentException if word is null or if pronunciation is
+   * an empty string
+   */
+  public static WordItemImpl valueOf(String word, String pronunciation) 
+          throws IllegalArgumentException
+  {
+    String[] pronunciations;
+    if (word == null)
+      throw new IllegalArgumentException("Word may not be null");
+    else if (pronunciation == null)
+      pronunciations = guessPronunciations;
+    else if (pronunciation.trim().equals(""))
+      throw new IllegalArgumentException(
+              "Pronunciation may not be an empty string");
+    else
+      pronunciations = new String[]{pronunciation};
+    return new WordItemImpl(word, pronunciations);
+  }
+
+  /**
+   * Allocates a reference to the native object.
+   *
+   * @param word the word to insert
+   */
+  private native void initNativeObject(String word, String[] pronunciations);
+
+  public long getNativeObject() 
+  {
+     synchronized (WordItemImpl.class)
+     {
+         return nativeObject;
+     }
+  }
+  
+  /**
+   * Releases the native resources associated with the object.
+   */
+  private void dispose()
+  {
+    synchronized (WordItemImpl.class)
+    {  
+        if (nativeObject != 0)
+        {
+            deleteNativeObject(nativeObject);
+            nativeObject = 0;
+        }
+    }
+  }
+
+  @Override
+  protected void finalize() throws Throwable
+  {
+    dispose();
+    super.finalize();
+  }
+
+  /**
+   * Deletes a native object.
+   *
+   * @param nativeObject pointer to the native object
+   */
+  private native void deleteNativeObject(long nativeObject);
+}
diff --git a/core/java/android/speech/recognition/impl/package.html b/core/java/android/speech/recognition/impl/package.html
new file mode 100755
index 0000000..1c9bf9d
--- /dev/null
+++ b/core/java/android/speech/recognition/impl/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/core/java/android/speech/recognition/package.html b/core/java/android/speech/recognition/package.html
new file mode 100644
index 0000000..3c59962
--- /dev/null
+++ b/core/java/android/speech/recognition/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+{@hide}
+Provides classes for speech recogntion.
+</BODY>
+</HTML>
diff --git a/core/java/android/speech/srec/Srec.java b/core/java/android/speech/srec/Srec.java
new file mode 100644
index 0000000..a629214
--- /dev/null
+++ b/core/java/android/speech/srec/Srec.java
@@ -0,0 +1,162 @@
+/*---------------------------------------------------------------------------*
+ *  EmbeddedRecognizerImpl.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;
+
+/**
+ * Simple, synchronous speech recognizer, using the SREC package.
+ * 
+ * @hide
+ */
+public class Srec
+{
+    private int mNative;
+    
+    
+    /**
+     * Create an instance of a SREC speech recognizer.
+     * @param configFile pathname of the baseline*.par configuration file.
+     * @throws IOException
+     */
+    Srec(String configFile) throws IOException {
+        
+    }
+    
+    /**
+     * Creates a Srec recognizer.
+     * @param g2gFileName pathname of a g2g grammar file.
+     * @return
+     * @throws IOException
+     */
+    public Grammar loadGrammar(String g2gFileName) throws IOException {
+        return null;
+    }
+    
+    /**
+     * Represents a grammar loaded into the recognizer.
+     */
+    public class Grammar {
+        private int mId = -1;
+        
+        /**
+         * Add a word to a slot
+         * @param slot slot name
+         * @param word word
+         * @param pron pronunciation, or null to derive from word
+         * @param weight weight to give the word
+         * @param meaning meaning string
+         */
+        public void addToSlot(String slot, String word, String pron, int weight, String meaning) {
+            
+        }
+        
+        /**
+         * Compile all slots.
+         */
+        public void compileSlots() {
+            
+        }
+        
+        /**
+         * Reset all slots.
+         */
+        public void resetAllSlots() {
+            
+        }
+        
+        /**
+         * Save grammar to g2g file.
+         * @param g2gFileName
+         * @throws IOException
+         */
+        public void save(String g2gFileName) throws IOException {
+            
+        }
+        
+        /**
+         * Release resources associated with this grammar.
+         */
+        public void unload() {
+            
+        }
+    }
+    
+    /**
+     * Start recognition
+     */
+    public void start() {
+        
+    }
+    
+    /**
+     * Process some audio and return the next state.
+     * @return true if complete
+     */
+    public boolean process() {
+        return false;
+    }
+    
+    /**
+     * Get the number of recognition results.
+     * @return
+     */
+    public int getResultCount() {
+        return 0;
+    }
+    
+    /**
+     * Get a set of keys for the result.
+     * @param index index of result.
+     * @return array of keys.
+     */
+    public String[] getResultKeys(int index) {
+        return null;
+    }
+    
+    /**
+     * Get a result value
+     * @param index index of the result.
+     * @param key key of the result.
+     * @return the result.
+     */
+    public String getResult(int index, String key) {
+        return null;
+    }
+    
+    /**
+     * Reset the recognizer to the idle state.
+     */
+    public void reset() {
+        
+    }
+    
+    /**
+     * Clean up resources.
+     */
+    public void dispose() {
+        
+    }
+    
+    protected void finalize() {
+        
+    }
+
+}
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..08a8ad1
--- /dev/null
+++ b/core/java/android/test/InstrumentationTestCase.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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}.  See
+ * <code>InstrumentationTestRunner</code>.
+ */
+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.
+     * @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);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName(pkg, activityCls.getName());
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        T activity = (T) getInstrumentation().startActivitySync(intent);
+        getInstrumentation().waitForIdleSync();
+        return activity;
+    }
+
+    /**
+     * Runs the current unit test. If the unit test is annotated with
+     * {@link android.test.UiThreadTest}, the test is run on the UI thread.
+     */
+    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/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..a3812a8
--- /dev/null
+++ b/core/java/android/text/Annotation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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 {
+    private String mKey;
+    private String mValue;
+
+    public Annotation(String key, String value) {
+        mKey = key;
+        mValue = value;
+    }
+
+    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..2ee4f62
--- /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) {
+            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) {
+            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..90f5e4c
--- /dev/null
+++ b/core/java/android/text/Html.java
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 {
+        mSpannableStringBuilder.append(CharBuffer.wrap(ch, start, length));
+    }
+
+    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..e1563ae
--- /dev/null
+++ b/core/java/android/text/InputFilter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     */
+    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/Layout.java b/core/java/android/text/Layout.java
new file mode 100644
index 0000000..346db49
--- /dev/null
+++ b/core/java/android/text/Layout.java
@@ -0,0 +1,1745 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+        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..dd2d77f
--- /dev/null
+++ b/core/java/android/text/LoginFilter.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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();
+        
+        return changed ? new String(out, 0, outidx) : null;
+    }
+    
+    /**
+     * 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/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/Selection.java b/core/java/android/text/Selection.java
new file mode 100644
index 0000000..0f4916a
--- /dev/null
+++ b/core/java/android/text/Selection.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+            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);
+        }
+    }
+
+    /*
+     * Public constants
+     */
+
+    public static final Object SELECTION_START = new Object();
+    public static final Object SELECTION_END = new Object();
+}
diff --git a/core/java/android/text/SpanWatcher.java b/core/java/android/text/SpanWatcher.java
new file mode 100644
index 0000000..f99882a
--- /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 {
+    /**
+     * 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..223ce2f
--- /dev/null
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -0,0 +1,1136 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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++) {
+                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..2b4b4d2
--- /dev/null
+++ b/core/java/android/text/Spanned.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;
+
+/**
+ * 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
+{
+    /**
+     * 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;
+
+    /**
+     * 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..2d18575
--- /dev/null
+++ b/core/java/android/text/StaticLayout.java
@@ -0,0 +1,1118 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+                        if (c == ' ' || c == '\t') {
+                            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);
+                            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);
+                            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 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 != null) {
+                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) {
+            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..05c27ea
--- /dev/null
+++ b/core/java/android/text/Styled.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.MaskFilter;
+import android.graphics.Rasterizer;
+import android.graphics.LayerRasterizer;
+import android.text.style.*;
+
+/* package */ 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 fm,
+                              TextPaint realPaint,
+                              TextPaint paint,
+                              boolean needwid) {
+
+        boolean havewid = false;
+        float ret = 0;
+        CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
+
+        ReplacementSpan replacement = null;
+
+        realPaint.bgColor = 0;
+        realPaint.baselineShift = 0;
+        paint.set(realPaint);
+
+		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(paint);
+				}
+			}
+		}
+
+        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 (fm != null) {
+                paint.getFontMetricsInt(fm);
+            }
+
+            if (canvas != null) {
+                if (paint.bgColor != 0) {
+                    int c = paint.getColor();
+                    Paint.Style s = paint.getStyle();
+                    paint.setColor(paint.bgColor);
+                    paint.setStyle(Paint.Style.FILL);
+
+                    if (!havewid) {
+                        ret = paint.measureText(tmp, tmpstart, tmpend);
+                        havewid = true;
+                    }
+
+                    if (dir == Layout.DIR_RIGHT_TO_LEFT)
+                        canvas.drawRect(x - ret, top, x, bottom, paint);
+                    else
+                        canvas.drawRect(x, top, x + ret, bottom, paint);
+
+                    paint.setStyle(s);
+                    paint.setColor(c);
+                }
+
+                if (dir == Layout.DIR_RIGHT_TO_LEFT) {
+                    if (!havewid) {
+                        ret = paint.measureText(tmp, tmpstart, tmpend);
+                        havewid = true;
+                    }
+
+                    canvas.drawText(tmp, tmpstart, tmpend,
+                                    x - ret, y + paint.baselineShift, paint);
+                } else {
+                    if (needwid) {
+                        if (!havewid) {
+                            ret = paint.measureText(tmp, tmpstart, tmpend);
+                            havewid = true;
+                        }
+                    }
+
+                    canvas.drawText(tmp, tmpstart, tmpend,
+                                    x, y + paint.baselineShift, paint);
+                }
+            } else {
+                if (needwid && !havewid) {
+                    ret = paint.measureText(tmp, tmpstart, tmpend);
+                    havewid = true;
+                }
+            }
+        } else {
+            ret = replacement.getSize(paint, text, start, end, fm);
+
+            if (canvas != null) {
+                if (dir == Layout.DIR_RIGHT_TO_LEFT)
+                    replacement.draw(canvas, text, start, end,
+                                     x - ret, top, y, bottom, paint);
+                else
+                    replacement.draw(canvas, text, start, end,
+                                     x, top, y, bottom, paint);
+            }
+        }
+
+        if (dir == Layout.DIR_RIGHT_TO_LEFT)
+            return -ret;
+        else
+            return ret;
+    }
+
+    public static int getTextWidths(TextPaint realPaint,
+                                    TextPaint paint,
+                              Spanned text, int start, int end,
+                              float[] widths, Paint.FontMetricsInt fm) {
+
+        MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class);
+
+		ReplacementSpan replacement = null;
+        paint.set(realPaint);
+		
+		for (int i = 0; i < spans.length; i++) {
+			MetricAffectingSpan span = spans[i];
+			if (span instanceof ReplacementSpan) {
+				replacement = (ReplacementSpan)span;
+			}
+			else {
+				span.updateMeasureState(paint);
+			}
+		}
+	
+        if (replacement == null) {
+            paint.getFontMetricsInt(fm);
+            paint.getTextWidths(text, start, end, widths);
+        } else {
+            int wid = replacement.getSize(paint, text, start, end, fm);
+
+            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 fm,
+                                 TextPaint paint,
+                                 TextPaint workPaint,
+                                 boolean needwid) {
+        if (! (text instanceof Spanned)) {
+            float ret = 0;
+
+            if (reverse) {
+                CharSequence tmp = TextUtils.getReverse(text, start, end);
+                int tmpend = end - start;
+
+                if (canvas != null || needwid)
+                    ret = paint.measureText(tmp, 0, tmpend);
+
+                if (canvas != null)
+                    canvas.drawText(tmp, 0, tmpend,
+                                    x - ret, y, paint);
+            } else {
+                if (needwid)
+                    ret = paint.measureText(text, start, end);
+
+                if (canvas != null)
+                    canvas.drawText(text, start, end, x, y, paint);
+            }
+
+            if (fm != null) {
+                paint.getFontMetricsInt(fm);
+            }
+
+            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, fm, paint, workPaint,
+                  needwid || next != end);
+
+            if (fm != null) {
+                if (fm.ascent < asc)
+                    asc = fm.ascent;
+                if (fm.descent > desc)
+                    desc = fm.descent;
+
+                if (fm.top < ftop)
+                    ftop = fm.top;
+                if (fm.bottom > fbot)
+                    fbot = fm.bottom;
+            }
+        }
+
+        if (fm != null) {
+            if (start == end) {
+                paint.getFontMetricsInt(fm);
+            } else {
+                fm.ascent = asc;
+                fm.descent = desc;
+                fm.top = ftop;
+                fm.bottom = fbot;
+            }
+        }
+
+        return x - ox;
+    }
+
+    public static float drawText(Canvas canvas,
+                                 CharSequence text, int start, int end,
+                                 int dir, boolean reverse,
+                                 float x, int top, int y, int bottom,
+                                 TextPaint paint,
+                                 TextPaint workPaint,
+                                 boolean needwid) {
+        if ((dir == Layout.DIR_RIGHT_TO_LEFT && !reverse)||(reverse && dir == 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 *= dir;  // DIR_RIGHT_TO_LEFT == -1
+            foreach(canvas, text, start, end, -dir,
+                    reverse, x + ch, top, y, bottom, null, paint,
+                    workPaint, true);
+
+            return ch;
+        }
+
+        return foreach(canvas, text, start, end, dir, reverse,
+                       x, top, y, bottom, null, paint, workPaint,
+                       needwid);
+    }
+
+    public static float measureText(TextPaint paint,
+                                    TextPaint workPaint,
+                                    CharSequence text, int start, int end,
+                                    Paint.FontMetricsInt fm) {
+        return foreach(null, text, start, end,
+                       Layout.DIR_LEFT_TO_RIGHT, false,
+                       0, 0, 0, 0, fm, 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..e791aaf
--- /dev/null
+++ b/core/java/android/text/TextUtils.java
@@ -0,0 +1,1570 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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;
+    }
+
+    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.
+     *
+     * @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) {
+        return a == b || (a != null && a.equals(b));
+    }
+
+    // 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;
+    }
+
+    private static final int ALIGNMENT_SPAN = 1;
+    private static final int FOREGROUND_COLOR_SPAN = 2;
+    private static final int RELATIVE_SIZE_SPAN = 3;
+    private static final int SCALE_X_SPAN = 4;
+    private static final int STRIKETHROUGH_SPAN = 5;
+    private static final int UNDERLINE_SPAN = 6;
+    private static final int STYLE_SPAN = 7;
+    private static final int BULLET_SPAN = 8;
+    private static final int QUOTE_SPAN = 9;
+    private static final int LEADING_MARGIN_SPAN = 10;
+    private static final int URL_SPAN = 11;
+    private static final int BACKGROUND_COLOR_SPAN = 12;
+    private static final int TYPEFACE_SPAN = 13;
+    private static final int SUPERSCRIPT_SPAN = 14;
+    private static final int SUBSCRIPT_SPAN = 15;
+    private static final int ABSOLUTE_SIZE_SPAN = 16;
+    private static final int TEXT_APPEARANCE_SPAN = 17;
+    private 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 AlignmentSpan) {
+                    p.writeInt(ALIGNMENT_SPAN);
+                    p.writeString(((AlignmentSpan) prop).getAlignment().name());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof ForegroundColorSpan) {
+                    p.writeInt(FOREGROUND_COLOR_SPAN);
+                    p.writeInt(((ForegroundColorSpan) prop).getForegroundColor());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof RelativeSizeSpan) {
+                    p.writeInt(RELATIVE_SIZE_SPAN);
+                    p.writeFloat(((RelativeSizeSpan) prop).getSizeChange());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof ScaleXSpan) {
+                    p.writeInt(SCALE_X_SPAN);
+                    p.writeFloat(((ScaleXSpan) prop).getScaleX());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof StrikethroughSpan) {
+                    p.writeInt(STRIKETHROUGH_SPAN);
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof UnderlineSpan) {
+                    p.writeInt(UNDERLINE_SPAN);
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof StyleSpan) {
+                    p.writeInt(STYLE_SPAN);
+                    p.writeInt(((StyleSpan) prop).getStyle());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof LeadingMarginSpan) {
+                    if (prop instanceof BulletSpan) {
+                        p.writeInt(BULLET_SPAN);
+                        writeWhere(p, sp, o);
+                    } else if (prop instanceof QuoteSpan) {
+                        p.writeInt(QUOTE_SPAN);
+                        p.writeInt(((QuoteSpan) prop).getColor());
+                        writeWhere(p, sp, o);
+                    } else {
+                        p.writeInt(LEADING_MARGIN_SPAN);
+                        p.writeInt(((LeadingMarginSpan) prop).
+                                           getLeadingMargin(true));
+                        p.writeInt(((LeadingMarginSpan) prop).
+                                           getLeadingMargin(false));
+                        writeWhere(p, sp, o);
+                    }
+                }
+
+                if (prop instanceof URLSpan) {
+                    p.writeInt(URL_SPAN);
+                    p.writeString(((URLSpan) prop).getURL());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof BackgroundColorSpan) {
+                    p.writeInt(BACKGROUND_COLOR_SPAN);
+                    p.writeInt(((BackgroundColorSpan) prop).getBackgroundColor());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof TypefaceSpan) {
+                    p.writeInt(TYPEFACE_SPAN);
+                    p.writeString(((TypefaceSpan) prop).getFamily());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof SuperscriptSpan) {
+                    p.writeInt(SUPERSCRIPT_SPAN);
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof SubscriptSpan) {
+                    p.writeInt(SUBSCRIPT_SPAN);
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof AbsoluteSizeSpan) {
+                    p.writeInt(ABSOLUTE_SIZE_SPAN);
+                    p.writeInt(((AbsoluteSizeSpan) prop).getSize());
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof TextAppearanceSpan) {
+                    TextAppearanceSpan tas = (TextAppearanceSpan) prop;
+                    p.writeInt(TEXT_APPEARANCE_SPAN);
+
+                    String tf = tas.getFamily();
+                    if (tf != null) {
+                        p.writeInt(1);
+                        p.writeString(tf);
+                    } else {
+                        p.writeInt(0);
+                    }
+
+                    p.writeInt(tas.getTextSize());
+                    p.writeInt(tas.getTextStyle());
+
+                    ColorStateList csl = tas.getTextColor();
+                    if (csl == null) {
+                        p.writeInt(0);
+                    } else {
+                        p.writeInt(1);
+                        csl.writeToParcel(p, parcelableFlags);
+                    }
+
+                    csl = tas.getLinkTextColor();
+                    if (csl == null) {
+                        p.writeInt(0);
+                    } else {
+                        p.writeInt(1);
+                        csl.writeToParcel(p, parcelableFlags);
+                    }
+
+                    writeWhere(p, sp, o);
+                }
+
+                if (prop instanceof Annotation) {
+                    p.writeInt(ANNOTATION);
+                    p.writeString(((Annotation) prop).getKey());
+                    p.writeString(((Annotation) prop).getValue());
+                    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(
+                            Layout.Alignment.valueOf(p.readString())));
+                    break;
+
+                case FOREGROUND_COLOR_SPAN:
+                    readSpan(p, sp, new ForegroundColorSpan(p.readInt()));
+                    break;
+
+                case RELATIVE_SIZE_SPAN:
+                    readSpan(p, sp, new RelativeSizeSpan(p.readFloat()));
+                    break;
+
+                case SCALE_X_SPAN:
+                    readSpan(p, sp, new ScaleXSpan(p.readFloat()));
+                    break;
+
+                case STRIKETHROUGH_SPAN:
+                    readSpan(p, sp, new StrikethroughSpan());
+                    break;
+
+                case UNDERLINE_SPAN:
+                    readSpan(p, sp, new UnderlineSpan());
+                    break;
+
+                case STYLE_SPAN:
+                    readSpan(p, sp, new StyleSpan(p.readInt()));
+                    break;
+
+                case BULLET_SPAN:
+                    readSpan(p, sp, new BulletSpan());
+                    break;
+
+                case QUOTE_SPAN:
+                    readSpan(p, sp, new QuoteSpan(p.readInt()));
+                    break;
+
+                case LEADING_MARGIN_SPAN:
+                    readSpan(p, sp, new LeadingMarginSpan.Standard(p.readInt(),
+                                                                   p.readInt()));
+                break;
+
+                case URL_SPAN:
+                    readSpan(p, sp, new URLSpan(p.readString()));
+                    break;
+
+                case BACKGROUND_COLOR_SPAN:
+                    readSpan(p, sp, new BackgroundColorSpan(p.readInt()));
+                    break;
+
+                case TYPEFACE_SPAN:
+                    readSpan(p, sp, new TypefaceSpan(p.readString()));
+                    break;
+
+                case SUPERSCRIPT_SPAN:
+                    readSpan(p, sp, new SuperscriptSpan());
+                    break;
+
+                case SUBSCRIPT_SPAN:
+                    readSpan(p, sp, new SubscriptSpan());
+                    break;
+
+                case ABSOLUTE_SIZE_SPAN:
+                    readSpan(p, sp, new AbsoluteSizeSpan(p.readInt()));
+                    break;
+
+                case TEXT_APPEARANCE_SPAN:
+                    readSpan(p, sp, new TextAppearanceSpan(
+                        p.readInt() != 0
+                            ? p.readString()
+                            : null,
+                        p.readInt(),
+                        p.readInt(),
+                        p.readInt() != 0
+                            ? ColorStateList.CREATOR.createFromParcel(p)
+                            : null,
+                        p.readInt() != 0
+                            ? ColorStateList.CREATOR.createFromParcel(p)
+                            : null));
+                    break;
+
+                case ANNOTATION:
+                    readSpan(p, sp,
+                             new Annotation(p.readString(), p.readString()));
+                    break;
+
+                default:
+                    throw new RuntimeException("bogus span encoding " + kind);
+                }
+            }
+
+            return sp;
+        }
+
+        public CharSequence[] newArray(int size)
+        {
+            return new CharSequence[size];
+        }
+    };
+
+    /**
+     * 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,
+    }
+
+    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;
+    }
+
+    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..7456b28
--- /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 {
+    /**
+     * 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/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
new file mode 100644
index 0000000..ac2e499
--- /dev/null
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.*;
+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;
+        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;
+        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;
+        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;
+        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) {
+        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;
+        }
+
+        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 onTrackballEvent(TextView widget, Spannable buffer,
+                                    MotionEvent event) {
+        boolean handled = false;
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+
+        for (; y < 0; y++) {
+            handled |= up(widget, buffer);
+        }
+        for (; y > 0; y--) {
+            handled |= down(widget, buffer);
+        }
+
+        for (; x < 0; x++) {
+            handled |= left(widget, buffer);
+        }
+        for (; x > 0; x--) {
+            handled |= right(widget, buffer);
+        }
+
+        if (handled) {
+            MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
+            MetaKeyKeyListener.resetLockedMeta(buffer);
+        }
+
+        return handled;
+    }
+
+    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 = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 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..3e92b7b
--- /dev/null
+++ b/core/java/android/text/method/BaseKeyListener.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.method;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.os.Message;
+import android.util.Log;
+import android.text.*;
+import android.widget.TextView;
+
+public abstract class BaseKeyListener
+extends MetaKeyKeyListener
+implements KeyListener {
+    /* package */ static final Object OLD_SEL_START = new Object();
+
+    /**
+     * 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;
+    }
+
+    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);
+    }
+}
+
diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java
new file mode 100644
index 0000000..d787132
--- /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_PANEL;
+
+        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..0ca0332
--- /dev/null
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * For entering dates in a text field.
+ */
+public class DateKeyListener extends NumberKeyListener
+{
+    @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..304d326
--- /dev/null
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * For entering dates and times in the same text field.
+ */
+public class DateTimeKeyListener extends NumberKeyListener
+{
+    @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..e805ad7
--- /dev/null
+++ b/core/java/android/text/method/DialerKeyListener.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.text.method;
+
+import android.view.KeyEvent;
+import android.view.KeyCharacterMap.KeyData;
+import android.util.SparseIntArray;
+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;
+    }
+
+    /**
+     * 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..99a3f1a
--- /dev/null
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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;
+    }
+
+    @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..05ab72d
--- /dev/null
+++ b/core/java/android/text/method/KeyListener.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.method;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.os.Message;
+import android.text.*;
+import android.widget.TextView;
+
+public interface KeyListener
+{
+    /**
+     * 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);
+}
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
new file mode 100644
index 0000000..92ac531
--- /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 Object();
+}
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
new file mode 100644
index 0000000..2d75b873
--- /dev/null
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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
+ * (caps, fn, sym).  Key listener 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;
+
+    public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8;
+    public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8;
+    public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8;
+
+    private static final Object CAP = new Object();
+    private static final Object ALT = new Object();
+    private static final Object SYM = new Object();
+
+    /**
+     * Resets all meta state to inactive.
+     */
+    public static void resetMetaState(Spannable text) {
+        text.removeSpan(CAP);
+        text.removeSpan(ALT);
+        text.removeSpan(SYM);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Gets the state of a particular meta key.
+     *
+     * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON
+     * @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);
+
+            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;
+    }
+
+    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);
+    }
+
+    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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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..9e37e59
--- /dev/null
+++ b/core/java/android/text/method/MovementMethod.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.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);
+    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..7137d40
--- /dev/null
+++ b/core/java/android/text/method/MultiTapKeyListener.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Message;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.widget.TextView;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+/**
+ * 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 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..348b658
--- /dev/null
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+import android.util.SparseIntArray;
+
+/**
+ * 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);
+        }
+
+        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..edaa836
--- /dev/null
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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]);
+                    }
+
+                    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> {
+        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..ae7ba8f
--- /dev/null
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Message;
+import android.os.Handler;
+import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.HashMap;
+
+/**
+ * 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 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 == '"' || i == ')' || i == ']') &&
+                 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);
+
+                        if (Character.isLetter(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
+    {
+        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..0438e1e
--- /dev/null
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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) {
+        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 onTouchEvent(TextView widget, Spannable buffer,
+                                 MotionEvent event) {
+        return Touch.onTouchEvent(widget, buffer, event);
+    }
+
+    public boolean onTrackballEvent(TextView widget, Spannable buffer,
+                                    MotionEvent event) {
+        boolean handled = false;
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+
+        for (; y < 0; y++) {
+            handled |= up(widget, buffer);
+        }
+        for (; y > 0; y--) {
+            handled |= down(widget, buffer);
+        }
+
+        for (; x < 0; x++) {
+            handled |= left(widget, buffer);
+        }
+        for (; x > 0; x--) {
+            handled |= right(widget, buffer);
+        }
+
+        return handled;
+    }
+
+    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..a4fcf15
--- /dev/null
+++ b/core/java/android/text/method/SingleLineTransformationMethod.java
@@ -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.
+ */
+
+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.
+ */
+public class SingleLineTransformationMethod
+extends ReplacementTransformationMethod {
+    private static char[] ORIGINAL = new char[] { '\n' };
+    private static char[] REPLACEMENT = new char[] { ' ' };
+
+    /**
+     * The character to be replaced is \n.
+     */
+    protected char[] getOriginal() {
+        return ORIGINAL;
+    }
+
+    /**
+     * The character \n is replaced with is 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..012e41d
--- /dev/null
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 Object();
+    /* package */ static final Object CAPPED = new Object();
+    /* package */ static final Object INHIBIT_REPLACEMENT = new Object();
+    /* package */ static final Object LAST_TYPED = new Object();
+
+    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;
+        }
+
+        // Back over allowed opening punctuation.
+
+        for (i = off; i > 0; i--) {
+            c = cs.charAt(i - 1);
+
+            if (c != '"' && c != '(' && c != '[' && c != '\'') {
+                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 true;
+        }
+
+        // Or start of word if we are that style.
+
+        if (cap == Capitalize.WORDS) {
+            return i != j;
+        }
+
+        // There must be a space if not the start of paragraph.
+
+        if (i == j) {
+            return false;
+        }
+
+        // Back over allowed closing punctuation.
+
+        for (; j > 0; j--) {
+            c = cs.charAt(j - 1);
+
+            if (c != '"' && c != ')' && c != ']' && c != '\'') {
+                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 false;
+                        }
+
+                        if (!Character.isLetter(c)) {
+                            break;
+                        }
+                    }
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @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);
+    }
+
+    /**
+     * 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 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 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..9ba1fe6
--- /dev/null
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * For entering times in a text field.
+ */
+public class TimeKeyListener extends NumberKeyListener
+{
+    @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..bd01728
--- /dev/null
+++ b/core/java/android/text/method/Touch.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.text.method;
+
+import android.text.Layout;
+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;
+
+        for (int i = top; i <= bottom; i++) {
+            left = (int) Math.min(left, layout.getLineLeft(i));
+            right = (int) Math.max(right, layout.getLineRight(i));
+        }
+
+        padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
+        x = Math.min(x, right - (widget.getWidth() - padding));
+        x = Math.max(x, left);
+
+        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.getTouchSlop();
+
+                    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 {
+        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..8f6ed5a
--- /dev/null
+++ b/core/java/android/text/style/AbsoluteSizeSpan.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.graphics.Paint;
+import android.text.TextPaint;
+
+public class AbsoluteSizeSpan extends MetricAffectingSpan {
+
+    private int mSize;
+
+    public AbsoluteSizeSpan(int size) {
+        mSize = size;
+    }
+
+    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..d51edcc
--- /dev/null
+++ b/core/java/android/text/style/AlignmentSpan.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.text.style;
+
+import android.text.Layout;
+
+public interface AlignmentSpan
+extends ParagraphStyle
+{
+    public Layout.Alignment getAlignment();
+
+    public static class Standard
+    implements AlignmentSpan
+    {
+        public Standard(Layout.Alignment align) {
+            mAlignment = align;
+        }
+
+        public Layout.Alignment getAlignment() {
+            return mAlignment;
+        }
+
+        private 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..be6ef77
--- /dev/null
+++ b/core/java/android/text/style/BackgroundColorSpan.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;
+
+import android.text.TextPaint;
+
+public class BackgroundColorSpan extends CharacterStyle {
+
+    private int mColor;
+
+	public BackgroundColorSpan(int color) {
+		mColor = color;
+	}
+
+	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..70c4d33
--- /dev/null
+++ b/core/java/android/text/style/BulletSpan.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.text.Layout;
+import android.text.Spanned;
+
+public class BulletSpan implements LeadingMarginSpan {
+
+    public BulletSpan() {
+        mGapWidth = STANDARD_GAP_WIDTH;
+    }
+
+    public BulletSpan(int gapWidth) {
+        mGapWidth = gapWidth;
+    }
+
+    public BulletSpan(int gapWidth, int color) {
+        mGapWidth = gapWidth;
+        mWantColor = true;
+        mColor = color;
+    }
+
+    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);
+        }
+    }
+
+    private int mGapWidth;
+    private boolean mWantColor;
+    private int mColor;
+
+    private static final int BULLET_RADIUS = 3;
+    public static final int STANDARD_GAP_WIDTH = 2;
+}
diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java
new file mode 100644
index 0000000..7620d29
--- /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.graphics.Paint;
+import android.text.TextPaint;
+
+/**
+ * The classes that affect character-level text formatting extend this
+ * class.  Most also extend {@link MetricAffectingSpan}.
+ */
+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..a232ed7
--- /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 {
+
+    /**
+     * 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..3bcc335
--- /dev/null
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 java.lang.ref.WeakReference;
+
+import android.graphics.drawable.Drawable;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+/**
+ *
+ */
+public abstract class DynamicDrawableSpan
+extends ReplacementSpan
+{
+    /**
+     * 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();
+
+    public int getSize(Paint paint, CharSequence text,
+                         int start, int end,
+                         Paint.FontMetricsInt fm) {
+        Drawable b = getCachedDrawable();
+
+        if (fm != null) {
+            fm.ascent = -b.getIntrinsicHeight();
+            fm.descent = 0;
+
+            fm.top = fm.ascent;
+            fm.bottom = 0;
+        }
+
+        return b.getIntrinsicWidth();
+    }
+
+    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();
+        
+        canvas.translate(x, bottom-b.getIntrinsicHeight());;
+        b.draw(canvas);
+        canvas.restore();
+    }
+
+    private Drawable getCachedDrawable() {
+        WeakReference wr = mDrawableRef;
+        Drawable b = null;
+
+        if (wr != null)
+            b = (Drawable) wr.get();
+
+        if (b == null) {
+            b = getDrawable();
+            mDrawableRef = new WeakReference(b);
+        }
+
+        return b;
+    }
+
+    private WeakReference 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..5cccd9c
--- /dev/null
+++ b/core/java/android/text/style/ForegroundColorSpan.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.Paint;
+import android.text.TextPaint;
+
+public class ForegroundColorSpan extends CharacterStyle {
+
+    private int mColor;
+
+	public ForegroundColorSpan(int color) {
+		mColor = color;
+	}
+
+	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..de067b1
--- /dev/null
+++ b/core/java/android/text/style/ImageSpan.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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) {
+        mDrawable = new BitmapDrawable(b);
+        mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(),
+                mDrawable.getIntrinsicHeight());
+    }
+
+    public ImageSpan(Drawable d) {
+        mDrawable = d;
+    }
+
+    public ImageSpan(Drawable d, String source) {
+        mDrawable = d;
+        mSource = source;
+    }
+
+    public ImageSpan(Context context, Uri uri) {
+        mContext = context;
+        mContentUri = uri;
+    }
+
+    public ImageSpan(Context context, int resourceId) {
+        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..85a27dc
--- /dev/null
+++ b/core/java/android/text/style/LeadingMarginSpan.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.style;
+
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.text.Layout;
+
+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
+    {
+        public Standard(int first, int rest) {
+            mFirst = first;
+            mRest = rest;
+        }
+
+        public Standard(int every) {
+            this(every, every);
+        }
+
+        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) {
+            ;
+        }
+
+        private int mFirst, mRest;
+    }
+}
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..781bcec
--- /dev/null
+++ b/core/java/android/text/style/MaskFilterSpan.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.text.style;
+
+import android.graphics.Paint;
+import android.graphics.MaskFilter;
+import android.text.TextPaint;
+
+public class MaskFilterSpan extends CharacterStyle {
+
+	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..3f4a32f
--- /dev/null
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.graphics.RectF;
+import android.text.Layout;
+
+public class QuoteSpan
+implements LeadingMarginSpan
+{
+    private static final int STRIPE_WIDTH = 2;
+    private static final int GAP_WIDTH = 2;
+
+    private int mColor = 0xff0000ff;
+
+    public QuoteSpan() {
+        super();
+    }
+    
+    public QuoteSpan(int color) {
+        this();
+        mColor = color;
+    }
+
+    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..193c700
--- /dev/null
+++ b/core/java/android/text/style/RasterizerSpan.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Rasterizer;
+import android.text.TextPaint;
+
+public class RasterizerSpan extends CharacterStyle {
+
+	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..a8ad076
--- /dev/null
+++ b/core/java/android/text/style/RelativeSizeSpan.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.text.TextPaint;
+
+public class RelativeSizeSpan extends MetricAffectingSpan {
+
+	private float mProportion;
+
+	public RelativeSizeSpan(float proportion) {
+		mProportion = proportion;
+	}
+
+	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..ac9e35d
--- /dev/null
+++ b/core/java/android/text/style/ScaleXSpan.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.text.TextPaint;
+
+public class ScaleXSpan extends MetricAffectingSpan {
+
+	private float mProportion;
+
+	public ScaleXSpan(float proportion) {
+		mProportion = proportion;
+	}
+
+	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..01ae38c
--- /dev/null
+++ b/core/java/android/text/style/StrikethroughSpan.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.text.style;
+
+import android.text.TextPaint;
+
+public class StrikethroughSpan extends CharacterStyle {
+
+	@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..cc8b06c
--- /dev/null
+++ b/core/java/android/text/style/StyleSpan.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.text.style;
+
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+
+/**
+ * 
+ * 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 {
+
+	private 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;
+	}
+
+	/**
+	 * 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..78d6ba9
--- /dev/null
+++ b/core/java/android/text/style/SubscriptSpan.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.text.style;
+
+import android.text.TextPaint;
+
+public class SubscriptSpan extends MetricAffectingSpan {
+    @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..79be4de
--- /dev/null
+++ b/core/java/android/text/style/SuperscriptSpan.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.text.style;
+
+import android.text.TextPaint;
+
+public class SuperscriptSpan extends MetricAffectingSpan {
+    @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..c4ec976
--- /dev/null
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Paint;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+
+/**
+ * Sets the text color, size, style, and typeface to match a TextAppearance
+ * resource.
+ */
+public class TextAppearanceSpan extends MetricAffectingSpan {
+    private String mTypeface;
+    private int mStyle;
+    private int mTextSize;
+    private ColorStateList mTextColor;
+    private 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) {
+        TypedArray a =
+            context.obtainStyledAttributes(appearance,
+                                           com.android.internal.R.styleable.TextAppearance);
+
+        mTextColor = 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;
+        }
+
+        a.recycle();
+
+        if (colorList >= 0) {
+            a = context.obtainStyledAttributes(com.android.internal.R.style.Theme,
+                                            com.android.internal.R.styleable.Theme);
+
+            mTextColor = a.getColorStateList(colorList);
+            a.recycle();
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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..7519ac2b
--- /dev/null
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.text.TextPaint;
+
+/**
+ * Changes the typeface family of the text to which the span is attached.
+ */
+public class TypefaceSpan extends MetricAffectingSpan {
+    private String mFamily;
+
+    /**
+     * @param family The font family for this typeface.  Examples include
+     * "monospace", "serif", and "sans-serif".
+     */
+    public TypefaceSpan(String family) {
+        mFamily = family;
+    }
+
+    /**
+     * 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..79809b5
--- /dev/null
+++ b/core/java/android/text/style/URLSpan.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.content.Intent;
+import android.net.Uri;
+import android.text.TextPaint;
+import android.view.View;
+
+public class URLSpan extends ClickableSpan {
+
+    private String mURL;
+
+    public URLSpan(String url) {
+        mURL = url;
+    }
+
+    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..5dce0f2
--- /dev/null
+++ b/core/java/android/text/style/UnderlineSpan.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.text.style;
+
+import android.text.TextPaint;
+
+public class UnderlineSpan extends CharacterStyle {
+
+	@Override
+	public void updateDrawState(TextPaint ds) {
+		ds.setUnderlineText(true);
+	}
+}
diff --git a/core/java/android/text/style/UpdateLayout.java b/core/java/android/text/style/UpdateLayout.java
new file mode 100644
index 0000000..211685a
--- /dev/null
+++ b/core/java/android/text/style/UpdateLayout.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 remove must implement
+ * this interface.
+ */
+public interface UpdateLayout { }
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..79ecfbd
--- /dev/null
+++ b/core/java/android/text/util/Linkify.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 phone numbers 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;
+                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..55ad140
--- /dev/null
+++ b/core/java/android/text/util/Regex.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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):\\/\\/(?:(?:[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  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.
+     */
+    public static final Pattern PHONE_PATTERN
+        = Pattern.compile(
+                "(?:\\+[0-9]+)|(?:[0-9()][0-9()\\- \\.][0-9()\\- \\.]+[0-9])");
+
+    /**
+     *  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..9f03bb0
--- /dev/null
+++ b/core/java/android/text/util/Rfc822Validator.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.text.util;
+
+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
+     * 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) {
+        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 everything right of the '@' and append the domain
+                // ("a@b" becomes "a@gmail.com").
+                String fix = removeIllegalCharacters(text.substring(0, index));
+                tokens[i].setAddress(fix + "@" + 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..52ee00e
--- /dev/null
+++ b/core/java/android/util/DayOfMonthCursor.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+    }
+    
+    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..8fc3602
--- /dev/null
+++ b/core/java/android/util/DisplayMetrics.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.util;
+
+
+/**
+ * A structure describing general information about a display, such as its
+ * size, density, and font scaling.
+ */
+public class DisplayMetrics {
+    /**
+     * 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). 
+     */
+    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 = 1;
+        scaledDensity = 1;
+        xdpi = 160;
+        ydpi = 160;
+    }
+}
+
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..24f67cd
--- /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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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 identfies
+     *        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/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..610cfd4
--- /dev/null
+++ b/core/java/android/util/SparseIntArray.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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) {
+            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, 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..3c4e337
--- /dev/null
+++ b/core/java/android/util/TimeUtils.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.util;
+
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+
+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;
+
+public class 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("TimeUtils",
+                  "Got exception while getting preferred time zone.", e);
+        } catch (IOException e) {
+            Log.e("TimeUtils",
+                  "Got exception while getting preferred time zone.", e);
+        } finally {
+            parser.close();
+        }
+        
+        return best;
+    }
+}
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..a4ee35a
--- /dev/null
+++ b/core/java/android/util/TypedValue.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Config;
+import android.util.Log;
+
+/**
+ * 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;
+
+    /* ------------------------------------------------------------ */
+
+    /** 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;
+    
+    /* ------------------------------------------------------------ */
+
+    /** 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;
+    }
+
+    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..9bfda40
--- /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, item icons, and sub menus.
+ * <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..4048763
--- /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.getEdgeSlop();
+        
+        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..fc9af05
--- /dev/null
+++ b/core/java/android/view/GestureDetector.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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);
+    }
+
+    /**
+     * 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} but does nothing and return {@code false}
+     * for all applicable methods.
+     */
+    public static class SimpleOnGestureListener implements OnGestureListener {
+        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;
+        }
+    }
+
+    private static final int TOUCH_SLOP_SQUARE = ViewConfiguration.getTouchSlop()
+            * ViewConfiguration.getTouchSlop();
+
+    // constants for Message.what used by GestureHandler below
+    private static final int SHOW_PRESS = 1;
+    private static final int LONG_PRESS = 2;
+
+    private final Handler mHandler;
+    private final OnGestureListener mListener;
+
+    private boolean mInLongPress;
+    private boolean mAlwaysInTapRegion;
+
+    private MotionEvent mCurrentDownEvent;
+    private MotionEvent mCurrentUpEvent;
+
+    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;
+
+            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, this must
+     * not be null.
+     *
+     * @throws NullPointerException if either {@code listener} or
+     * {@code handler} is null.
+     */
+    public GestureDetector(OnGestureListener listener, Handler handler) {
+        mHandler = new GestureHandler(handler);
+        mListener = listener;
+        init();        
+    }
+
+    /**
+     * 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.
+     */
+    public GestureDetector(OnGestureListener listener) {
+        mHandler = new GestureHandler();
+        mListener = listener;
+        init();
+    }
+
+    private void init() {
+        if (mListener == null) {
+            throw new NullPointerException("OnGestureListener must not be null");
+        }
+        mIsLongpressEnabled = true;
+    }
+
+    /**
+     * 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 long tapTime = ViewConfiguration.getTapTimeout();
+        final long longpressTime = ViewConfiguration.getLongPressTimeout();
+        final int touchSlop = ViewConfiguration.getTouchSlop();
+        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:
+            mLastMotionX = x;
+            mLastMotionY = y;
+            mCurrentDownEvent = MotionEvent.obtain(ev);
+            mAlwaysInTapRegion = true;
+            mInLongPress = false;
+
+            if (mIsLongpressEnabled) {
+                mHandler.removeMessages(LONG_PRESS);
+                mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+                        + tapTime + longpressTime);
+            }
+            mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + tapTime);
+            handled = mListener.onDown(ev);
+            break;
+
+        case MotionEvent.ACTION_MOVE:
+            if (mInLongPress) {
+                break;
+            }
+            final float scrollX = mLastMotionX - x;
+            final float scrollY = mLastMotionY - y;
+            if (mAlwaysInTapRegion) {
+                final int deltaX = (int) (x - mCurrentDownEvent.getX());
+                final int deltaY = (int) (y - mCurrentDownEvent.getY());
+                int distance = (deltaX * deltaX) + (deltaY * deltaY);
+                if (distance > TOUCH_SLOP_SQUARE) {
+                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
+                    mLastMotionX = x;
+                    mLastMotionY = y;
+                    mAlwaysInTapRegion = false;
+                    mHandler.removeMessages(SHOW_PRESS);
+                    mHandler.removeMessages(LONG_PRESS);
+                }
+            } 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:
+            mCurrentUpEvent = MotionEvent.obtain(ev);
+            if (mInLongPress) {
+                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) > ViewConfiguration.getMinimumFlingVelocity())
+                        || (Math.abs(velocityX) > ViewConfiguration.getMinimumFlingVelocity())){
+                    handled = mListener.onFling(mCurrentDownEvent, mCurrentUpEvent, velocityX, velocityY);
+                }
+            }
+            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);
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+            if (mInLongPress) {
+                mInLongPress = false;
+                break;
+            }
+        }
+        return handled;
+    }
+
+    private void dispatchLongPress() {
+        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..ff9ab18
--- /dev/null
+++ b/core/java/android/view/Gravity.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.view;
+import android.graphics.Rect;
+
+/**
+ * Standard constants and tools for placing an object within a potentially
+ * larger container.
+ */
+public class Gravity
+{
+    /** Contstant 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;
+
+    /** 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 obejct if needed so it
+     *  completely fills its container. */
+    public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL;
+
+    /**
+     * 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;
+
+    /**
+     * 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) {
+        if ((gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT))
+             == ((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
+            outRect.left = container.left;
+            outRect.right = container.right;
+        } else {
+            outRect.left = applyMovement(
+                gravity>>AXIS_X_SHIFT, w, container.left, container.right, xAdj);
+            outRect.right = outRect.left + w;
+        }
+
+        if ((gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT))
+             == ((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
+            outRect.top = container.top;
+            outRect.bottom = container.bottom;
+        } else {
+            outRect.top = applyMovement(
+                gravity>>AXIS_Y_SHIFT, h, container.top, container.bottom, yAdj);
+            outRect.bottom = outRect.top + h;
+        }
+    }
+
+    /**
+     * <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;
+    }
+
+    private static int applyMovement(int mode, int size,
+            int start, int end, int adj) {
+        if ((mode & AXIS_PULL_BEFORE) != 0) {
+            return start + adj;
+        }
+
+        if ((mode & AXIS_PULL_AFTER) != 0) {
+            return end - size - adj;
+        }
+
+        return start + ((end - start - size)/2) + adj;
+    }
+}
+
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..b4a3067
--- /dev/null
+++ b/core/java/android/view/IWindow.aidl
@@ -0,0 +1,57 @@
+/* //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.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, 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..e6d52e2
--- /dev/null
+++ b/core/java/android/view/IWindowManager.aidl
@@ -0,0 +1,125 @@
+/* //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 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(IBinder token);
+
+    // 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);
+    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);
+
+    // 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);
+    
+    // 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..c2c0b97
--- /dev/null
+++ b/core/java/android/view/IWindowSession.aidl
@@ -0,0 +1,73 @@
+/* //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 outCoveredInsets);
+    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 outFrame Object in which is placed the new position/size on
+     *                 screen.
+     * @param outCoveredInsets Object in which is placed the insets for the areas covered by
+ 	 *                 system windows (e.g. status bar)
+     * @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,
+            out Rect outFrame, out Rect outCoveredInsets, out Surface outSurface);
+
+    void finishDrawing(IWindow window);
+
+    void finishKey(IWindow window);
+    MotionEvent getPendingPointerMove(IWindow window);
+    MotionEvent getPendingTrackballMove(IWindow window);
+    
+    void setTransparentRegion(IWindow window, in Region region);
+
+    void setInTouchMode(boolean showFocus);
+    boolean getInTouchMode();
+}
+
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..0347d50
--- /dev/null
+++ b/core/java/android/view/KeyCharacterMap.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.view;
+
+import android.text.method.MetaKeyKeyListener;
+import android.util.SparseIntArray;
+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);
+    }
+
+    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..24b0316
--- /dev/null
+++ b/core/java/android/view/KeyEvent.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.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;
+
+    // NOTE: If you add a new keycode here you must also add it to:
+    //  isSystem()
+    //  include/ui/KeycodeLabels.h
+    //  tools/puppet_master/PuppetMaster/nav_keys.py
+    //  apps/common/res/values/attrs.xml
+    //  commands/monkey/Monkey.java
+    //  emulator?
+    
+    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.  The {#link {@link #getRepeatCount()} method returns
+     * the number of duplicates.
+     */
+    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;
+    
+    /**
+     * 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;
+
+    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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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_POWER:
+        case KEYCODE_HEADSETHOOK:
+        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 -- not the Unicode character.
+     * 
+     * @return The key code of the event.
+     */
+    public final int getKeyCode() {
+        return mKeyCode;
+    }
+
+    /**
+     * 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;
+                }
+                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..f2ec076
--- /dev/null
+++ b/core/java/android/view/Menu.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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, item icons, and sub
+ * menus.
+ * <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();
+
+    /**
+     * 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..92cf4af
--- /dev/null
+++ b/core/java/android/view/MenuItem.java
@@ -0,0 +1,368 @@
+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..ab05e16
--- /dev/null
+++ b/core/java/android/view/MotionEvent.java
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 {
+    public static final int ACTION_DOWN             = 0;
+    public static final int ACTION_UP               = 1;
+    public static final int ACTION_MOVE             = 2;
+    public static final int ACTION_CANCEL           = 3;
+
+    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/OrientationListener.java b/core/java/android/view/OrientationListener.java
new file mode 100644
index 0000000..0add025
--- /dev/null
+++ b/core/java/android/view/OrientationListener.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+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 OrientationListener implements SensorListener {
+
+    private static final String TAG = "OrientationListener";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    private SensorManager mSensorManager;
+    private int mOrientation = ORIENTATION_UNKNOWN;
+    private boolean mEnabled = false;
+    
+    /**
+     * 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 OrientationListener.
+     * 
+     * @param context for the OrientationListener.
+     */
+    public OrientationListener(Context context) {
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+    }
+
+    /**
+     * Enables the OrientationListener so it will monitor the sensor and call
+     * {@link #onOrientationChanged} when the device orientation changes.
+     */
+    public void enable() {
+        if (mEnabled == false) {
+            if (localLOGV) Log.d(TAG, "OrientationListener enabled");
+            mSensorManager.registerListener(this, SensorManager.SENSOR_ACCELEROMETER);
+            mEnabled = true;
+        }
+    }
+
+    /**
+     * Disables the OrientationListener.
+     */
+    public void disable() {
+        if (mEnabled == true) {
+            if (localLOGV) Log.d(TAG, "OrientationListener disabled");
+            mSensorManager.unregisterListener(this);
+            mEnabled = false;
+        }
+    }
+
+    /**
+     * 
+     */
+    public void onSensorChanged(int sensor, float[] values) {
+        int orientation = ORIENTATION_UNKNOWN;
+        float X = values[SensorManager.RAW_DATA_X];
+        float Y = values[SensorManager.RAW_DATA_Y];
+        float Z = values[SensorManager.RAW_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 (orientation != mOrientation) {
+            mOrientation = orientation;
+            onOrientationChanged(orientation);
+        }
+    }
+
+    public void onAccuracyChanged(int sensor, int accuracy) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    /**
+     * 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/RawInputEvent.java b/core/java/android/view/RawInputEvent.java
new file mode 100644
index 0000000..580a80d
--- /dev/null
+++ b/core/java/android/view/RawInputEvent.java
@@ -0,0 +1,169 @@
+/**
+ * 
+ */
+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_TOUCHSCREEN = 0x00000002;
+    public static final int CLASS_TRACKBALL = 0x00000004;
+    
+    // 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/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..21a72e7
--- /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 android.view.SurfaceHolder.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..57689f2
--- /dev/null
+++ b/core/java/android/view/SurfaceView.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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 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 mWinFrame = new Rect();
+    final Rect mCoveredInsets = 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();
+                    mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+                    mLayout.gravity = Gravity.LEFT|Gravity.TOP;
+                    mSession.add(mWindow, mLayout,
+                            mVisible ? VISIBLE : GONE, mCoveredInsets);
+                }
+                
+                if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
+                    reportSurfaceDestroyed();
+                }
+                
+                mNewSurfaceNeeded = false;
+                
+                mSurfaceLock.lock();
+                mDrawingStopped = !visible;
+                final int relayoutResult = mSession.relayout(
+                    mWindow, mLayout, mWidth, mHeight,
+                    visible ? VISIBLE : GONE, mWinFrame, mCoveredInsets, 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 class MyWindow extends IWindow.Stub {
+        public void resized(int w, int h, boolean reportDraw) {
+            if (localLOGV) Log.v(
+                "SurfaceView", SurfaceView.this + " 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 {
+                        mSession.finishDrawing(mWindow);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+
+        public void dispatchKey(KeyEvent event) {
+            //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
+            if (mSession != null && mSurface != null) {
+                try {
+                    mSession.finishKey(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() {
+            Message msg = mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
+            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..057df92
--- /dev/null
+++ b/core/java/android/view/TouchDelegate.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+    
+    /**
+     * 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;
+        
+        int slop = ViewConfiguration.getTouchSlop();
+        mSlopBounds = new Rect(bounds);
+        mSlopBounds.inset(-slop, -slop);
+        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 = ViewConfiguration.getTouchSlop();
+                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..30402f8
--- /dev/null
+++ b/core/java/android/view/View.java
@@ -0,0 +1,7481 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.SystemClock;
+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.widget.ScrollBarDrawable;
+
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * <p>
+ * The <code>View</code> class represents the basic UI building block. A view
+ * occupies a rectangular area on the screen and is responsible for drawing and
+ * event handling. <code>View</code> is the base class for <em>widgets</em>,
+ * used to create interactive graphical user interfaces. 
+ * </p>
+ * 
+ * <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.
+ * </p>
+ * 
+ * <p>
+ * The framework will not draw views that are not in the invalid region, and also 
+ * will take care of drawing the views background for you.
+ * </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;
+
+    /**
+     * 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};
+    
+    /**
+     * 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;
+
+    /**
+     * Used to store a pair of coordinates, for instance returned values
+     * returned by {@link #getLocationInWindow(int[])}.
+     * 
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    protected final int[] mLocation = new int[2];
+
+    /**
+     * 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;
+    /** {@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;
+
+    // Note: flag 0x00000040 is available
+    
+    /**
+     * The parent this view is attached to.
+     * {@hide}
+     *
+     * @see #getParent()
+     */
+    protected ViewParent mParent;
+
+    /**
+     * {@hide}
+     */
+    AttachInfo mAttachInfo;
+
+    /**
+     * {@hide}
+     */
+    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}
+     */
+    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 Bitmap mDrawingCache;
+
+    /**
+     * Used for local (within a stackframe) calls that need a rect temporarily
+     */ 
+    private final Rect mTempRect = new Rect();
+
+    /**
+     * 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;
+    
+    /**
+     * 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.
+     */
+    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.
+     */
+    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;
+        ++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;
+
+        int x = 0;
+        int y = 0;
+
+        int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
+
+        viewFlagValues |= SOUND_EFFECTS_ENABLED;
+        viewFlagMasks |= SOUND_EFFECTS_ENABLED;      
+
+        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 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 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);
+        }
+
+        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.getFadingEdgeLength());
+    }
+    
+    /**
+     * 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();
+        }
+
+        mScrollCache.scrollBarSize = a.getDimensionPixelSize(
+                com.android.internal.R.styleable.View_scrollbarSize,
+                ViewConfiguration.getScrollBarSize());
+
+        Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal);
+        mScrollCache.scrollBar.setHorizontalTrackDrawable(track);
+
+        Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal);
+        if (thumb != null) {
+            mScrollCache.scrollBar.setHorizontalThumbDrawable(thumb);
+        }
+        
+        boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack, 
+                false);
+        if (alwaysDraw) {
+            mScrollCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
+        }
+
+        track = a.getDrawable(R.styleable.View_scrollbarTrackVertical);
+        mScrollCache.scrollBar.setVerticalTrackDrawable(track);
+
+        thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical);
+        if (thumb != null) {
+            mScrollCache.scrollBar.setVerticalThumbDrawable(thumb);
+        }
+        
+        alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack, 
+                false);
+        if (alwaysDraw) {
+            mScrollCache.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();
+        }
+    }
+
+    /**
+     * 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();
+        }
+        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.
+     *
+     * 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.
+     *
+     * 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.
+     *
+     * 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 instanceof ViewGroup) {
+            ViewGroup vgParent = (ViewGroup) parent;
+            scrolled |= vgParent.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());
+
+            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.
+     * 
+     * @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) {
+        if (!gainFocus) {
+            if (isPressed()) {
+                setPressed(false);
+            }
+        }
+        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;
+    }
+
+    /**
+     * 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 ViewRoot) {
+                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;
+            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
+     */
+    public void setVisibility(int visibility) {
+        setFlags(visibility, VISIBILITY_MASK);
+    }
+
+    /**
+     * 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.
+     *
+     * 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);
+    }
+
+    /**
+     * 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;
+    }
+    
+    /**
+     * 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 (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) {
+        if (!hasWindowFocus) {
+            if (isPressed()) {
+                setPressed(false);
+            }
+        }
+        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;
+    }
+    
+    /**
+     * Private function to aggregate all per-view attributes in to the view
+     * root.
+     */
+    void dispatchCollectViewAttributes(int visibility) {
+        performCollectViewAttributes(visibility);
+    }
+
+    void performCollectViewAttributes(int visibility) {
+        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.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * 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;
+    }
+    
+    /**
+     * 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();
+                            }
+                        }
+
+                        final UnsetPressedState unsetPressedState = new UnsetPressedState();
+                        if (!post(unsetPressedState)) {
+                            // If the post failed, unpress right now
+                            unsetPressedState.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.getTouchSlop();
+                    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();
+            }
+        }
+
+        /* 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 ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
+            if (mDrawingCache != null) {
+                mDrawingCache.recycle();
+            }
+            mDrawingCache = null;
+        }
+
+        if ((changed & DRAWING_CACHE_ENABLED) != 0) {
+            if (mDrawingCache != null) {
+                mDrawingCache.recycle();
+            }
+            mDrawingCache = null;
+            mPrivateFlags &= ~DRAWING_CACHE_VALID;
+        }
+
+        if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
+            if (mDrawingCache != null) {
+                mDrawingCache.recycle();
+            }
+            mDrawingCache = null;
+            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;
+    }
+
+    /**
+     * 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.
+     */
+    public final int getTop() {
+        return mTop;
+    }
+    
+    /**
+     * Bottom position of this view relative to its parent.
+     * 
+     * @return The bottom of this view, in pixels.
+     */
+    public final int getBottom() {
+        return mBottom;
+    }
+    
+    /**
+     * Left position of this view relative to its parent.
+     * 
+     * @return The left edge of this view, in pixels.
+     */
+    public final int getLeft() {
+        return mLeft;
+    }
+    
+    /**
+     * Right position of this view relative to its parent.
+     * 
+     * @return The right edge of this view, in pixels.
+     */
+    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;
+            ViewParent p = mParent;
+            if (p != null) {
+                final int scrollX = mScrollX;
+                final int scrollY = mScrollY;
+                mTempRect.set(dirty.left - scrollX, dirty.top - scrollY,
+                              dirty.right - scrollX, dirty.bottom - scrollY);
+                p.invalidateChild(this, mTempRect);
+            }
+        }
+    }
+
+    /**
+     * 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;
+            ViewParent p = mParent;
+            if (p != null && l < r && t < b) {
+                final int scrollX = mScrollX;
+                final int scrollY = mScrollY;
+                mTempRect.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
+                p.invalidateChild(this, mTempRect);
+            }
+        }
+    }
+
+    /**
+     * 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;
+            ViewParent p = mParent;
+            if (p != null) {
+                mTempRect.set(0, 0, mRight - mLeft, mBottom - mTop);
+                // Don't call invalidate -- we don't want to internally scroll
+                // our own bounds
+                p.invalidateChild(this, mTempRect);
+            }
+        }
+    }
+    
+    /**
+     * @return A handler associated with the thread running the View. This
+     * handler can be used to pump events in the UI events queue.
+     */
+    protected 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 {
+            handler = ViewRoot.sUiThreads.get();
+            if (handler == null) {
+                // Assume that post will succeed later
+                ViewRoot.sRunQueue.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 {
+            handler = ViewRoot.sUiThreads.get();
+            if (handler == null) {
+                // Assume that post will succeed later
+                ViewRoot.sRunQueue.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 {
+            handler = ViewRoot.sUiThreads.get();
+            if (handler == null) {
+                // Assume that post will succeed later
+                ViewRoot.sRunQueue.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() {
+        // 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.sendMessage(msg);
+        }
+    }
+
+    /**
+     * 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) {
+        // 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_RECT_MSG;
+            msg.obj = this;
+            msg.arg1 = (left << 16) | (top & 0xFFFF);
+            msg.arg2 = (right << 16) | (bottom & 0xFFFF);
+            mAttachInfo.mHandler.sendMessage(msg);
+        }
+    }
+
+    /**
+     * 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) {
+            Message msg = Message.obtain();
+            msg.what = AttachInfo.INVALIDATE_RECT_MSG;
+            msg.obj = this;
+            msg.arg1 = (left << 16) | (top & 0xFFFF);
+            msg.arg2 = (right << 16) | (bottom & 0xFFFF);
+            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;
+
+            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);
+                }
+            }
+        }
+    }
+
+    /**
+     * <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);
+        }
+    }
+    
+    /**
+     * @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;
+        }
+        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();
+        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 &&
+            ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null)) {
+            buildDrawingCache();
+        }
+        return mDrawingCache;
+    }
+
+    /**
+     * <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) {
+            mDrawingCache.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) {
+            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.getMaximumDrawingCacheSize())) {
+                if (mDrawingCache != null) {
+                    mDrawingCache.recycle();
+                }
+                mDrawingCache = null;
+                return;
+            }
+
+            boolean clear = true;
+            Bitmap bitmap = mDrawingCache;
+
+            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;
+                }
+
+                mDrawingCache = bitmap = Bitmap.createBitmap(width, height, quality);
+
+                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);
+
+            // Fast path for layouts with no backgrounds
+            if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                mPrivateFlags |= DRAWN;
+                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;
+        }
+    }
+    
+
+    /**
+     * 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);
+        }
+
+        /*
+         * 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
+            mPrivateFlags |= DRAWN;
+            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
+        final int paddingLeft = mPaddingLeft;
+        final int paddingTop = mPaddingTop;
+
+        final int left = mScrollX + paddingLeft;
+        final int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+        final int top = mScrollY + paddingTop;
+        final int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;
+
+        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
+        mPrivateFlags |= DRAWN;
+        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;
+
+        boolean isPressed = (privateFlags & PRESSED) != 0;
+        int viewStateIndex = (isPressed ? 1 : 0);
+
+        boolean isEnabled = (mViewFlags & ENABLED_MASK) == ENABLED;
+        viewStateIndex = (viewStateIndex << 1) + (isEnabled ? 1 : 0);
+
+        boolean isFocused = isFocused();
+        viewStateIndex = (viewStateIndex << 1) + (isFocused ? 1 : 0);
+        
+        boolean isSelected = (privateFlags & SELECTED) != 0;
+        viewStateIndex = (viewStateIndex << 1) + (isSelected ? 1 : 0);
+        
+        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=" + isPressed
+                    + " en=" + isEnabled + " fo=" + isFocused
+                    + " sl=" + isSelected + " 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) {
+            final Rect padding = mTempRect;
+            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() {
+        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;
+
+        if (!(mParent instanceof View)) {
+            return;
+        }
+
+        View parent = (View) mParent;
+
+        while (parent != null) {
+            location[0] += parent.mLeft - parent.mScrollX;
+            location[1] += parent.mTop - parent.mScrollY;
+
+            final ViewParent viewParent = parent.mParent;
+            if (viewParent != null && viewParent instanceof View) {
+                parent = (View) viewParent;
+            } else {
+                parent = null;
+            }
+        }
+    }
+
+    /**
+     * {@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
+     */
+    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) {
+        if (region != 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.
+                getLocationInWindow(mLocation);
+                region.op(mLocation[0], mLocation[1],
+                        mLocation[0] + mRight - mLeft, mLocation[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.
+     *
+     * 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.
+     *
+     * 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}
+     */
+    protected void playSoundEffect(int soundConstant) {
+        if (mAttachInfo == null || mAttachInfo.mSoundEffectPlayer == null || !isSoundEffectsEnabled()) {
+            return;
+        }
+        mAttachInfo.mSoundEffectPlayer.playSoundEffect(soundConstant);
+    }
+
+    /**
+     * 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();
+        if (r != 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);
+            }
+            getLocationInWindow(mLocation);
+            r.translate(mLocation[0], mLocation[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 #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 SoundEffectPlayer {
+            void playSoundEffect(int effectId);
+        }
+
+        IBinder mWindowToken;
+        IBinder mPanelParentWindowToken;
+        Surface mSurface;
+        IWindowSession mSession;
+        SoundEffectPlayer mSoundEffectPlayer;
+
+        /**
+         * Left position of this view's window
+         */
+        int mWindowLeft;
+
+        /**
+         * Top position of this view's window
+         */
+        int mWindowTop;
+
+        /**
+         * 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;
+        
+        /**
+         * 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;
+
+        AttachInfo(Handler handler) {
+            this(handler, null);
+        }
+
+        /**
+         * Creates a new set of attachment information with the specified
+         * events handler and thread.
+         *
+         * @param handler the events handler the view must use
+         */
+        AttachInfo(Handler handler, SoundEffectPlayer effectPlayer) {
+            mHandler = handler;
+            mSoundEffectPlayer = 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 = ViewConfiguration.getFadingEdgeLength();
+
+        public int scrollBarSize = ViewConfiguration.getScrollBarSize();
+        public ScrollBarDrawable scrollBar;
+
+        public final Paint paint;
+        public final Matrix matrix;
+        public Shader shader;
+
+        private int mLastColor = 0;
+
+        public ScrollabilityCache() {
+            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);
+                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
new file mode 100644
index 0000000..38be806
--- /dev/null
+++ b/core/java/android/view/ViewConfiguration.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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 = 6;
+
+    /**
+     * 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 we will wait to see if a touch event 
+     * is a top 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 we want to display zoom controls in response 
+     * to a user panning within an application.
+     */
+    private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
+
+    /**
+     * 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 = 1000;
+    
+    
+    /**
+     * 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 = 12;
+    
+    /**
+     * 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.
+     */
+    private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // One HVGA screen, ARGB8888
+
+    /**
+     * The coefficient of friction applied to flings/scrolls.
+     */
+    private static float SCROLL_FRICTION = 0.015f;
+
+    /**
+     * @return The width of the horizontal scrollbar and the height of the vertical
+     *         scrollbar in pixels
+     */
+    public static int getScrollBarSize() {
+        return SCROLL_BAR_SIZE;
+    }
+
+    /**
+     * @return Defines the length of the fading edges in pixels
+     */
+    public static int getFadingEdgeLength() {
+        return FADING_EDGE_LENGTH;
+    }
+    
+    /**
+     * @return Defines the duration in milliseconds of the pressed state in child
+     * components.
+     */
+    public static int getPressedStateDuration() {
+        return PRESSED_STATE_DURATION;
+    }
+    
+    /**
+     * @return Defines the duration in milliseconds before a press turns into
+     * a long press
+     */
+    public static int getLongPressTimeout() {
+        return LONG_PRESS_TIMEOUT;
+    }
+    
+    /**
+     * @return Defines the duration in milliseconds we will wait to see if a touch event 
+     * is a top 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 Defines 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 Inset in pixels to look for touchable content when the user touches the edge of the
+     *         screen
+     */
+    public static int getEdgeSlop() {
+        return EDGE_SLOP;
+    }
+    
+    /**
+     * @return Distance a touch can wander before we think the user is scrolling in pixels
+     */
+    public static int getTouchSlop() {
+        return 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 static int getWindowTouchSlop() {
+        return WINDOW_TOUCH_SLOP;
+    }
+    
+    /**
+     * Minimum velocity to initiate a fling, as measured in pixels per second
+     */
+    public static int getMinimumFlingVelocity() {    
+     return MINIMUM_FLING_VELOCITY;
+    }
+
+    /**
+     * The maximum drawing cache size expressed in bytes.
+     *
+     * @return the maximum size of View's drawing cache expressed in bytes
+     */
+    public static int getMaximumDrawingCacheSize() {
+        return MAXIMUM_DRAWING_CACHE_SIZE;
+    }
+
+    /**
+     * 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..1bf46b4
--- /dev/null
+++ b/core/java/android/view/ViewDebug.java
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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;
+
+    /**
+     * 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();
+    }
+
+    // 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>/tmp/view-recycler/PREFIX.traces</code> and
+     * <code>/tmp/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>/tmp/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>/tmp/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("/tmp/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("/tmp/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>/tmp/view-hierarchy/PREFIX.traces</code> and
+     * <code>/tmp/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>/tmp/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("/tmp/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>/tmp/view-hierarchy/PREFIX.traces</code>. 
+     *
+     * Calling this method creates the file <code>/tmp/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("/tmp/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) {
+                            methodValue = resources.getResourceTypeName(id) + '/' +
+                                    resources.getResourceEntryName(id);
+                        } 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) {
+                            fieldValue = resources.getResourceTypeName(id) + '/' +
+                                    resources.getResourceEntryName(id);
+                        } 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;
+    }
+}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
new file mode 100644
index 0000000..9063821
--- /dev/null
+++ b/core/java/android/view/ViewGroup.java
@@ -0,0 +1,3389 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.Parcelable;
+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 final Transformation mChildTransformation = new Transformation();
+
+    // 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}.
+     * 
+     * This flag needs to be removed until we can add a setter for it.  People
+     * can't be directly stuffing values in to 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;
+    }
+
+    /**
+     * 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) {
+        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 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 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() {
+        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);
+
+        }
+
+        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) {
+            mChildTransformation.clear();
+            mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
+        }
+
+        Transformation transformToApply = null;
+        final Animation a = child.getAnimation();
+        boolean concatMatrix = false;
+
+        if (a != null) {
+            if (!a.isInitialized()) {
+                a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
+                child.onAnimationStart();
+            }
+
+            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) {
+                        invalidate(cl, ct, cr, cb);
+                    }
+                } else {
+                    mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+                }
+            }
+        } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
+                FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
+            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)) {
+            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 + cr - cl, sy + cb - ct);
+            } else {
+                canvas.clipRect(0, 0, cr - cl, cb - ct);
+            }
+        }
+
+        if (hasNoCache) {
+            // Fast path for layouts with no backgrounds
+            if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                child.mPrivateFlags |= DRAWN;
+                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;
+            }
+            child.mPrivateFlags |= DRAWN;
+            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);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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 (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 (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 theparent. 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 int[] location = mLocation;
+        location[CHILD_LEFT_INDEX] = child.mLeft;
+        location[CHILD_TOP_INDEX] = child.mTop;
+
+        do {
+            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 &= ~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 (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..1a5d495
--- /dev/null
+++ b/core/java/android/view/ViewParent.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
new file mode 100644
index 0000000..ca67404
--- /dev/null
+++ b/core/java/android/view/ViewRoot.java
@@ -0,0 +1,2212 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.*;
+import android.os.Process;
+import android.util.AndroidRuntimeException;
+import android.util.Config;
+import android.util.Log;
+import android.util.EventLog;
+import android.view.View.MeasureSpec;
+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"})
+final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.SoundEffectPlayer {
+    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_ORIENTATION = false || LOCAL_LOGV;
+    private static final boolean DEBUG_TRACKBALL = 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;
+
+    private static long sInstanceCount = 0;
+
+    private static IWindowSession sWindowSession;
+
+    private static final Object mStaticInit = new Object();
+    private static boolean mInitialized = false;
+
+    static final ThreadLocal<Handler> sUiThreads = new ThreadLocal<Handler>();
+    static final RunQueue sRunQueue = new RunQueue();
+
+    private long mLastTrackballTime = 0;
+    private final TrackballAxis mTrackballAxisX = new TrackballAxis();
+    private final TrackballAxis mTrackballAxisY = new TrackballAxis();
+
+    private final Thread mThread;
+
+    private final WindowLeaked mLocation;
+
+    private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
+
+    final W mWindow;
+
+    private View mView;
+    private View mFocusedView;
+    private int mViewVisibility;
+    private boolean mAppVisible = true;
+
+    private final Region mTransparentRegion;
+    private final Region mPreviousTransparentRegion;
+
+    private int mWidth;
+    private int mHeight;
+    private Rect mDirty; // will be a graphics.Region soon
+
+    private final View.AttachInfo mAttachInfo;
+
+    private final Rect mTempRect; // used in the transaction to not thrash the heap.
+
+    private boolean mTraversalScheduled;
+    private boolean mWillDrawSoon;
+    private boolean mLayoutRequested;
+    private boolean mFirst;
+    private boolean mReportNextDraw;
+    private boolean mFullRedrawNeeded;
+    private boolean mNewSurfaceNeeded;
+
+    private boolean mWindowAttributesChanged = false;
+
+    // These can be accessed by any thread, must be protected with a lock.
+    private Surface mSurface;
+
+    private boolean mAdded;
+    private boolean mAddedTouchMode;
+
+    /*package*/ int mAddNesting;
+
+    // These are accessed by multiple threads.
+    private final Rect mWinFrame; // frame given by window manager.
+
+    private final Rect mCoveredInsets = new Rect();
+    private final Rect mNewCoveredInsets = new Rect();
+
+    private EGL10 mEgl;
+    private EGLDisplay mEglDisplay;
+    private EGLContext mEglContext;
+    private EGLSurface mEglSurface;
+    private GL11 mGL;
+    private Canvas mGlCanvas;
+    private boolean mUseGL;
+    private boolean mGlWanted;
+
+    /**
+     * see {@link #playSoundEffect(int)}
+     */
+    private AudioManager mAudioManager;
+
+
+
+    public ViewRoot() {
+        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 {
+                    sWindowSession = IWindowManager.Stub.asInterface(
+                            ServiceManager.getService("window"))
+                            .openSession(new Binder());
+                    mInitialized = true;
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        mThread = Thread.currentThread();
+        mLocation = new WindowLeaked(null);
+        mLocation.fillInStackTrace();
+        mWidth = -1;
+        mHeight = -1;
+        mDirty = new Rect();
+        mTempRect = new Rect();
+        mWinFrame = new Rect();
+        mWindow = new W(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;
+
+        Handler handler = sUiThreads.get();
+        if (handler == null) {
+            handler = new RootHandler();
+            sUiThreads.set(handler);
+        }
+        mAttachInfo = new View.AttachInfo(handler, this);
+    }
+
+    @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);
+                mWindowAttributesChanged = true;
+                mView = 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(), mCoveredInsets);
+                } catch (RemoteException e) {
+                    mAdded = false;
+                    mView = null;
+                    unscheduleTraversals();
+                    throw new RuntimeException("Adding window failed", e);
+                }
+                if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
+                if (res < WindowManagerImpl.ADD_OKAY) {
+                    mView = 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;
+    }
+
+    public void setLayoutParams(WindowManager.LayoutParams attrs) {
+        synchronized (this) {
+            mWindowAttributes.copyFrom(attrs);
+            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);
+        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!");
+        }
+        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 = (WindowManager.LayoutParams) host.getLayoutParams();
+
+        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 = mWindowAttributes;
+        }
+
+        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.mWindowToken = mWindow.asBinder();
+            attachInfo.mSurface = mSurface;
+            attachInfo.mSession = sWindowSession;
+            attachInfo.mHasWindowFocus = false;
+            attachInfo.mWindowVisibility = viewVisibility;
+            attachInfo.mRecomputeGlobalAttributes = false;
+            attachInfo.mKeepScreenOn = false;
+            viewVisibilityChanged = false;
+            host.dispatchAttachedToWindow(attachInfo, 0);
+            sRunQueue.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 (mLayoutRequested) {
+            if (mFirst) {
+                host.fitSystemWindows(mCoveredInsets);
+                // make sure touch mode code executes by setting cached value
+                // to opposite of the added touch mode.
+                mAttachInfo.mInTouchMode = !mAddedTouchMode;
+                ensureTouchModeLocally(mAddedTouchMode);
+            } else {
+                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) 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 = mWindowAttributes;
+                //Log.i(TAG, "Keep screen on changed: " + attachInfo.mKeepScreenOn);
+            }
+        }
+
+        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);
+
+        int relayoutResult = 0;
+        if (mFirst || windowShouldResize || viewVisibilityChanged || params != null) {
+
+            if (viewVisibility == View.VISIBLE) {
+                if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) {
+                    if (params == null) {
+                        params = mWindowAttributes;
+                    }
+                    mGlWanted = true;
+                }
+            }
+
+            final Rect frame = mWinFrame;
+            boolean initialized = false;
+            boolean coveredInsetsChanged = 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, frame, mNewCoveredInsets, mSurface);
+                if (params != null) {
+                    params.flags = fl;
+                }
+
+                coveredInsetsChanged = !mNewCoveredInsets.equals(mCoveredInsets);
+                if (coveredInsetsChanged) {
+                    mCoveredInsets.set(mNewCoveredInsets);
+                    host.fitSystemWindows(mCoveredInsets);
+                }
+
+                if (!hadSurface && 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;
+                    }
+                }
+            } 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 || coveredInsetsChanged) {
+                childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
+                childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+
+                 // 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) {
+                    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+                }
+
+                mLayoutRequested = true;
+            }
+        }
+
+        boolean triggerGlobalLayoutListener = mLayoutRequested
+                || attachInfo.mRecomputeGlobalAttributes;
+        if (mLayoutRequested) {
+            mLayoutRequested = false;
+            if (DEBUG_ORIENTATION) Log.v(
+                "ViewRoot", "Setting frame " + 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(host.mLocation);
+                mTransparentRegion.set(host.mLocation[0], host.mLocation[1],
+                        host.mLocation[0] + host.mRight - host.mLeft,
+                        host.mLocation[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 (mFirst) {
+            // handle first focus request
+            if (mView != null && !mView.hasFocus()) {
+                mView.requestFocus(View.FOCUS_FORWARD);
+                mFocusedView = mView.findFocus();
+            }
+        }
+
+        mFirst = false;
+        mWillDrawSoon = false;
+        mNewSurfaceNeeded = false;
+        mViewVisibility = viewVisibility;
+
+        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, rememeber 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;
+        }
+
+        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();
+                    mView.draw(canvas);
+
+                    mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
+                    checkEglErrors();
+
+                    if (SHOW_FPS) {
+                        int now = (int)SystemClock.elapsedRealtime();
+                        if (sDrawTime != 0) {
+                            nativeShowFPS(canvas, now - sDrawTime);
+                        }
+                        sDrawTime = now;
+                    }
+                }
+            }
+            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());
+        }
+
+        if (!dirty.isEmpty()) {
+            Canvas canvas;
+            try {
+                canvas = surface.lockCanvas(dirty);
+            } 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;
+            }
+
+            long startTime;
+
+            try {
+                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(0, PorterDuff.Mode.CLEAR);
+                }
+
+                dirty.setEmpty();
+                mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+                mView.draw(canvas);
+
+                if (SHOW_FPS) {
+                    int now = (int)SystemClock.elapsedRealtime();
+                    if (sDrawTime != 0) {
+                        nativeShowFPS(canvas, now - sDrawTime);
+                    }
+                    sDrawTime = now;
+                }
+
+            } finally {
+                surface.unlockCanvasAndPost(canvas);
+            }
+
+            if (PROFILE_DRAWING) {
+                EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+            }
+
+            if (LOCAL_LOGV) {
+                Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
+            }
+        }
+    }
+
+    public void requestChildFocus(View child, View focused) {
+        checkThread();
+        if (mFocusedView != focused) {
+            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
+        }
+        mFocusedView = focused;
+    }
+
+    public void clearChildFocus(View child) {
+        checkThread();
+
+        View oldFocus = mFocusedView;
+
+        mFocusedView = 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;
+        if (mUseGL) {
+            destroyGL();
+        }
+    }
+    
+    /**
+     * 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);
+    }
+
+
+    private final static int DO_TRAVERSAL = 1000;
+    private final static int DIE = 1001;
+    private final static int RESIZED = 1002;
+    private final static int RESIZED_REPORT = 1003;
+    private final static int WINDOW_FOCUS_CHANGED = 1004;
+    private final static int DISPATCH_KEY = 1005;
+    private final static int DISPATCH_POINTER = 1006;
+    private final static int DISPATCH_TRACKBALL = 1007;
+    private final static int DISPATCH_APP_VISIBILITY = 1008;
+    private final static int DISPATCH_GET_NEW_SURFACE = 1009;
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+        case DO_TRAVERSAL:
+            if (mProfile) {
+                Debug.startMethodTracing("ViewRoot");
+            }
+
+            performTraversals();
+
+            if (mProfile) {
+                Debug.stopMethodTracing();
+                mProfile = false;
+            }
+            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 = false;
+            }
+
+            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);
+                    }
+
+                    handled = mView.dispatchTouchEvent(event);
+                    if (!handled && isDown) {
+                        int edgeSlop = ViewConfiguration.getEdgeSlop();
+
+                        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:
+            if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2) {
+                break;
+            }
+            // fall through...
+        case RESIZED_REPORT:
+            if (mAdded) {
+                mWinFrame.left = 0;
+                mWinFrame.right = msg.arg1;
+                mWinFrame.top = 0;
+                mWinFrame.bottom = msg.arg2;
+                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);
+                            }
+                        }
+                    }
+                }
+                if (mView != null) {
+                    mView.dispatchWindowFocusChanged(hasWindowFocus);
+                }
+            }
+        } break;
+        case DIE:
+            dispatchDetachedFromWindow();
+            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;
+        }
+
+        //Log.i("foo", "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 someting?
+                }
+            }
+        } finally {
+            if (handled) {
+                if (!didFinish) {
+                    try {
+                        sWindowSession.finishKey(mWindow);
+                    } catch (RemoteException e) {
+                    }
+                }
+                if (event != null) {
+                    event.recycle();
+                }
+                //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(), "X");
+            final float yOff = y.collect(event.getY(), "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);
+                //Log.i(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;
+        }
+
+        // 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;
+    }
+
+
+    private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
+        try {
+            if (mView != null && mAdded) {
+                final int action = event.getAction();
+                boolean isDown = (action == KeyEvent.ACTION_DOWN);
+
+                if (checkForLeavingTouchModeAndConsume(event)) {
+                    return;
+                }
+
+                boolean keyHandled = mView.dispatchKeyEvent(event);
+
+                if ((!keyHandled && isDown) || (action == KeyEvent.ACTION_MULTIPLE)) {
+                    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 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, mWinFrame, mCoveredInsets, mSurface)
+                            &WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+                            sWindowSession.finishDrawing(mWindow);
+                        }
+                    } catch (RemoteException e) {
+                    }
+                }
+
+                mSurface = null;
+            }
+            if (mAdded) {
+                mAdded = false;
+                try {
+                    sWindowSession.remove(mWindow);
+                } catch (RemoteException e) {
+                }
+                if (immediate) {
+                    dispatchDetachedFromWindow();
+                } else if (mView != null) {
+                    sendEmptyMessage(DIE);
+                }
+            }
+        }
+    }
+
+    public void dispatchResized(int w, int h, boolean reportDraw) {
+        if (DEBUG_DRAW) Log.v(TAG, "Resized " + this + ": w=" + w
+                + " h=" + h + " reportDraw=" + reportDraw);
+        Message msg = obtainMessage(reportDraw ? RESIZED_REPORT : RESIZED);
+        msg.arg1 = w;
+        msg.arg2 = h;
+        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
+    }
+
+    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, boolean reportDraw) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchResized(w, h, reportDraw);
+            }
+        }
+
+        public void dispatchKey(KeyEvent event) {
+            final ViewRoot viewRoot = mViewRoot.get();
+            if (viewRoot != null) {
+                viewRoot.dispatchKey(event);
+            }
+        }
+
+        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 {
+        float position;
+        float absPosition;
+        float acceleration = 1;
+        int step;
+        int dir;
+        int nonAccelMovement;
+
+        void reset(int _step) {
+            position = 0;
+            acceleration = 1;
+            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, String axis) {
+            if (off > 0) {
+                if (dir < 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                }
+                dir = 1;
+            } else if (off < 0) {
+                if (dir > 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                }
+                dir = -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.  The acceleration is
+                    // currently very simple, just reducing the amount of
+                    // trackball motion required as more discrete movements are
+                    // generated.  This should probably be changed to take time
+                    // more into account, so that quick trackball movements will
+                    // have increased acceleration.
+                    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 < 20 ? acc : acceleration;
+                        break;
+                }
+            } while (true);
+        }
+    }
+
+    public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
+        public CalledFromWrongThreadException(String msg) {
+            super(msg);
+        }
+    }
+
+    private static final class RootHandler extends Handler {
+        @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:
+                    int left = msg.arg1 >>> 16;
+                    int top = msg.arg1 & 0xFFFF;
+                    int right = msg.arg2 >>> 16;
+                    int bottom = msg.arg2 & 0xFFFF;
+                    ((View) msg.obj).invalidate(left, top, right, bottom);
+                    break;
+            }
+        }
+    }
+
+    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;
+        }
+    };
+
+    /**
+     * @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;
+                final int count = actions.size();
+
+                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) {
+                return action.equals(o);
+            }
+        }
+    }
+
+    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..2e1e01a
--- /dev/null
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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);
+    }
+
+    /**
+     * 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;
+            }
+        }
+
+        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 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);
+    }
+
+    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);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
new file mode 100644
index 0000000..24f4853
--- /dev/null
+++ b/core/java/android/view/VolumePanel.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.media.ToneGenerator;
+import android.media.AudioManager;
+import android.media.AudioService;
+import android.content.Context;
+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 final String RINGTONE_VOLUME_TEXT;
+    private final String MUSIC_VOLUME_TEXT;
+    private final String INCALL_VOLUME_TEXT;
+    private final String ALARM_VOLUME_TEXT;
+    private final String UNKNOWN_VOLUME_TEXT;
+    
+    protected Context mContext;
+    protected AudioService mAudioService;
+    
+    private Toast mToast;
+    private View mView;
+    private TextView mMessage;
+    private ImageView mOtherStreamIcon;
+    private ImageView mRingerStreamIcon;
+    private ProgressBar mLevel;
+
+    // Synchronize when accessing this
+    private ToneGenerator mToneGenerators[];
+    private Vibrator mVibrator;
+    
+    public VolumePanel(Context context, AudioService volumeService) {
+        mContext = context;
+        mAudioService = volumeService;
+        mToast = new Toast(context);
+    
+        RINGTONE_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_ringtone);
+        MUSIC_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_music);
+        INCALL_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_call);
+        ALARM_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_alarm);
+        UNKNOWN_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_unknown);
+        
+        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);
+        mOtherStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon);
+        mRingerStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon);
+        mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level);
+
+        mToneGenerators = new ToneGenerator[AudioManager.NUM_STREAMS];
+        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);
+        String message = UNKNOWN_VOLUME_TEXT;
+        
+        if (LOGD) {
+            Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
+                    + ", flags: " + flags + "), index: " + index);
+        }
+
+        switch (streamType) {
+            
+            case AudioManager.STREAM_RING: {
+                message = RINGTONE_VOLUME_TEXT;
+                setRingerIcon(index);
+                break;
+            }
+                
+            case AudioManager.STREAM_MUSIC: {
+                message = MUSIC_VOLUME_TEXT;
+                setOtherIcon(index);
+                break;
+            }
+                
+            case AudioManager.STREAM_VOICE_CALL: {
+                message = INCALL_VOLUME_TEXT;
+                /*
+                 * For in-call voice call volume, there is no inaudible volume
+                 * level, so never show the mute icon
+                 */
+                setOtherIcon(index == 0 ? 1 : index);
+                break;
+            }
+
+            case AudioManager.STREAM_ALARM: {
+                message = ALARM_VOLUME_TEXT;
+                setOtherIcon(index);
+                break;
+            }
+        }
+
+        if (!mMessage.getText().equals(message)) {
+            mMessage.setText(message);
+        }
+
+        int max = mAudioService.getStreamMaxVolume(streamType);
+        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) {
+            for (int i = AudioManager.NUM_STREAMS - 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];
+            }
+        }
+    }
+    
+    private void setOtherIcon(int index) {
+        mRingerStreamIcon.setVisibility(View.GONE);
+        mOtherStreamIcon.setVisibility(View.VISIBLE);
+        
+        mOtherStreamIcon.setImageResource(index == 0
+                ? com.android.internal.R.drawable.ic_volume_off_small
+                : com.android.internal.R.drawable.ic_volume_small);
+    }
+
+    private void setRingerIcon(int index) {
+        mOtherStreamIcon.setVisibility(View.GONE);
+        mRingerStreamIcon.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;
+        }
+        mRingerStreamIcon.setImageResource(icon);
+    }
+    
+    protected void onFreeResources() {
+        // We'll keep the views, just ditch the cached drawable and hence
+        // bitmaps
+        mOtherStreamIcon.setImageDrawable(null);
+        mRingerStreamIcon.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..4aeab2d
--- /dev/null
+++ b/core/java/android/view/Window.java
@@ -0,0 +1,962 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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;
+
+    // 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.windowAnimations == 0) {
+                    wp.windowAnimations = getWindowStyle().getResourceId(
+                            com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+                }
+            }
+            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);
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param a The new window attributes, which will completely override any
+     *          current values.
+     */
+    public void setAttributes(WindowManager.LayoutParams a) {
+        mWindowAttributes.copyFrom(a);
+        mForcedWindowFlags = 0xffffffff;
+        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;
+    }
+    
+    /**
+     * 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..4c1dec5
--- /dev/null
+++ b/core/java/android/view/WindowManager.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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_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
+         */
+        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;
+    
+        /**
+         * 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;
+        
+        /**
+         * 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 focus. */
+        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: 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;
+
+        /**
+         * 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;
+    
+        /**
+         * 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;
+        
+        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(gravity);
+            out.writeFloat(horizontalMargin);
+            out.writeFloat(verticalMargin);
+            out.writeInt(format);
+            out.writeInt(windowAnimations);
+            out.writeFloat(alpha);
+            out.writeFloat(dimAmount);
+            out.writeStrongBinder(token);
+            out.writeString(packageName);
+            TextUtils.writeToParcel(mTitle, out, parcelableFlags);
+        }
+        
+        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();
+            gravity = in.readInt();
+            horizontalMargin = in.readFloat();
+            verticalMargin = in.readFloat();
+            format = in.readInt();
+            windowAnimations = in.readInt();
+            alpha = in.readFloat();
+            dimAmount = in.readFloat();
+            token = in.readStrongBinder();
+            packageName = in.readString();
+            mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        }
+    
+        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 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 (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 (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;
+            }
+    
+            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() {
+            return "WM.LayoutParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " (" + x + "," + y + ")("
+                + (width==FILL_PARENT?"fill_parent":(width==WRAP_CONTENT?"wrap_content":width))
+                + "x"
+                + (height==FILL_PARENT?"fill_parent":(height==WRAP_CONTENT?"wrap_content":height))
+                + ") gr=#" + Integer.toHexString(gravity)
+                + " ty=" + type + " fl=#" + Integer.toHexString(flags)
+                + " fmt=" + format + "}";
+        }
+    
+        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..fbecf46
--- /dev/null
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+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);
+                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();
+            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);
+        }
+    }
+
+    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;
+        }
+
+        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 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..93e1a0b
--- /dev/null
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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 parentLeft The left edge of the parent container this window
+         * is in, used for computing its position.
+         * @param parentTop The top edge of the parent container this window
+         * is in, used for computing its position.
+         * @param parentRight The right edge of the parent container this window
+         * is in, used for computing its position.
+         * @param parentBottom The bottom edge of the parent container this window
+         * is in, used for computing its position.
+         * @param displayLeft The left edge of the available display, used
+         * for constraining the overall dimensions of the window.
+         * @param displayTop The left edge of the available display, used
+         * for constraining the overall dimensions of the window.
+         * @param displayRight The right edge of the available display, used
+         * for constraining the overall dimensions of the window.
+         * @param displayBottom The bottom edge of the available display, used
+         * for constraining the overall dimensions of the window.
+         */
+        public void computeFrameLw(int parentLeft, int parentRight, int parentBottom,
+                int parentHeight, int displayLeft, int displayTop,
+                int displayRight, int displayBottom);
+
+        /**
+         * Set the window's frame to an exact value.  Must be called with the
+         * window manager lock held.
+         * 
+         * @param left Left edge of the window.
+         * @param top Top edge of the window.
+         * @param right Right edge (exclusive) of the window.
+         * @param bottom Bottom edge (exclusive) of the window.
+         */
+        public void setFrameLw(int left, int top, int right, int bottom);
+
+        /**
+         * Retrieve the current frame of the window.  Must be called with the
+         * window manager lock held.
+         * 
+         * @return Rect The rectangle holding the window frame.
+         */
+        public Rect getFrameLw();
+
+        /**
+         * Retrieve the current LayoutParams of the window.
+         * 
+         * @return WindowManager.LayoutParams The window's internal LayoutParams
+         *         instance.
+         */
+        public WindowManager.LayoutParams getAttrs();
+
+        /**
+         * 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 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.
+         * @return Returns true if the window is both full screen and opaque
+         */
+        public boolean fillsScreenLw(int width, int height, boolean shownFrame);
+
+        /**
+         * 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.
+         */
+        public void hideLw();
+        
+        /**
+         * 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.
+         */
+        public void showLw();
+
+        /**
+         * Sets insets on the window that represent the area within the window that is covered
+         * by system windows (e.g. status bar).  Must be called with the window
+         * manager lock held.
+         * 
+         * @param l
+         * @param t
+         * @param r
+         * @param b
+         */
+        public void setCoveredInsetsLw(int l, int t, int r, int b);
+    }
+
+    /** 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
+     * mose recent layout, so they are not guaranteed to be correct.
+     * 
+     * @param attrs The LayoutParams of the window.
+     * @param coveredInset The areas covered by system windows, expressed as positive insets
+     * 
+     */
+    public void getCoveredInsetHintLw(WindowManager.LayoutParams attrs, Rect coveredInset);
+    
+    /**
+     * 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);
+    
+    /**
+     * 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);
+}
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..39fe561
--- /dev/null
+++ b/core/java/android/view/animation/Animation.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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+/**
+ * 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 {
+    /**
+     * 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;
+
+    /**
+     * 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;
+    
+    // Indicates what was the last value returned by getTransformation()
+    private boolean mMore = true;
+
+    /**
+     * 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));
+        
+        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();
+    }
+
+    /**
+     * Reset the initialization state of this animation.
+     *
+     * @see #initialize(int, int, int, int)
+     */
+    public void reset() {
+        mInitialized = false;
+        mCycleFlip = false;
+        mRepeated = 0;
+        mMore = 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) {
+        mInitialized = true;
+        mCycleFlip = false;
+        mRepeated = 0;
+        mMore = 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.
+     * 
+     * @param durationMillis Duration in milliseconds
+     * @attr ref android.R.styleable#Animation_duration
+     */
+    public void setDuration(long durationMillis) {
+        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 fillBefore is true, this animation will apply its transformation
+     * before the start time of the animation. 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 fillBefore true if the animation should apply its transformation before it starts
+     * @attr ref android.R.styleable#Animation_fillBefore
+     */
+    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
+     */
+    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();
+        }
+    }
+
+    /**
+     * 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();
+        float normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
+                    (float) mDuration;
+
+        boolean expired = normalizedTime >= 1.0f;
+        // Pin time to 0.0 to 1.0 range
+        normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
+        mMore = !expired;
+
+        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
+            if (!mStarted) {
+                if (mListener != null) {
+                    mListener.onAnimationStart(this);
+                }
+                mStarted = true;
+            }
+
+            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);
+                }
+            }
+        }
+
+        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;
+        }
+    }
+    
+    /**
+     * 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 invoked
+         * only for animation with repeat mode set to NO_REPEAT.</p>
+         *
+         * @param animation The animation which reached its end.
+         */
+        void onAnimationEnd(Animation animation);
+
+        /**
+         * <p>Notifies the repetition of the animation. This callback is invoked
+         * only for animation with repeat mode set to RESTART or REVERSE.</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..3c5920f
--- /dev/null
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 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();
+    }
+
+    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;
+        }
+
+        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 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;
+        
+        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) {
+                a.setStartOffset(startOffset);
+            }
+            a.initialize(width, height, parentWidth, parentHeight);
+        }
+    }
+
+    /**
+     * @hide
+     * @param startOffset the startOffset to add to the children's startOffset
+     */
+    void saveChildrenStartOffset(long startOffset) {
+        final ArrayList<Animation> children = mAnimations;
+        final int count = children.size();
+        long[] storedOffsets = mStoredOffsets = new long[count];
+
+        for (int i = 0; i < count; i++) {
+            Animation animation = children.get(i);
+            long offset = animation.getStartOffset();
+            animation.setStartOffset(offset + startOffset);
+            storedOffsets[i] = offset;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void restoreChildrenStartOffset() {
+        final ArrayList<Animation> children = mAnimations;
+        final int count = children.size();
+        final long[] offsets = mStoredOffsets;
+
+        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;
+    }
+}
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..9cfa8d7
--- /dev/null
+++ b/core/java/android/view/animation/LayoutAnimationController.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+        mMaxDelay = Math.max(mMaxDelay, delay);
+        return new DelayedAnimation(delay, mAnimation);
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Encapsulates an animation and delays its start offset by a specified
+     * amount. This allows to reuse the same base animation for various views
+     * and get the effect of running multiple instances of the animation at
+     * different times.
+     */
+    private static class DelayedAnimation extends Animation {
+        private final long mDelay;
+        private final Animation mAnimation;
+
+        /**
+         * Creates a new delayed animation that will delay the controller's
+         * animation by the specified delay in milliseconds.
+         *
+         * @param delay the delay in milliseconds by which to offset the
+         * @param animation the animation to delay
+         */
+        private DelayedAnimation(long delay, Animation animation) {
+            mDelay = delay;
+            mAnimation = animation;
+        }
+
+        @Override
+        public boolean isInitialized() {
+            return mAnimation.isInitialized();
+        }
+
+        @Override
+        public void initialize(int width, int height, int parentWidth, int parentHeight) {
+            mAnimation.initialize(width, height, parentWidth, parentHeight);
+        }
+
+        @Override
+        public void reset() {
+            mAnimation.reset();
+        }
+
+        @Override
+        public boolean getTransformation(long currentTime, Transformation outTransformation) {
+            final long oldOffset = mAnimation.getStartOffset();
+            final boolean isSet = mAnimation instanceof AnimationSet;
+            if (isSet) {
+                AnimationSet set = ((AnimationSet) mAnimation);
+                set.saveChildrenStartOffset(mDelay);
+            }
+            mAnimation.setStartOffset(oldOffset + mDelay);
+
+            boolean result = mAnimation.getTransformation(currentTime,
+                    outTransformation);
+            
+            if (isSet) {
+                AnimationSet set = ((AnimationSet) mAnimation);
+                set.restoreChildrenStartOffset();
+            }
+            mAnimation.setStartOffset(oldOffset);
+
+            return result;
+        }
+
+        @Override
+        public void setStartTime(long startTimeMillis) {
+            mAnimation.setStartTime(startTimeMillis);
+        }
+
+        @Override
+        public long getStartTime() {
+            return mAnimation.getStartTime();
+        }
+
+        @Override
+        public void setInterpolator(Interpolator i) {
+            mAnimation.setInterpolator(i);
+        }
+
+        @Override
+        public void setStartOffset(long startOffset) {
+            mAnimation.setStartOffset(startOffset);
+        }
+
+        @Override
+        public void setDuration(long durationMillis) {
+            mAnimation.setDuration(durationMillis);
+        }
+
+        @Override
+        public void scaleCurrentDuration(float scale) {
+            mAnimation.scaleCurrentDuration(scale);
+        }
+
+        @Override
+        public void setRepeatMode(int repeatMode) {
+            mAnimation.setRepeatMode(repeatMode);
+        }
+
+        @Override
+        public void setFillBefore(boolean fillBefore) {
+            mAnimation.setFillBefore(fillBefore);
+        }
+
+        @Override
+        public void setFillAfter(boolean fillAfter) {
+            mAnimation.setFillAfter(fillAfter);
+        }
+
+        @Override
+        public Interpolator getInterpolator() {
+            return mAnimation.getInterpolator();
+        }
+
+        @Override
+        public long getDuration() {
+            return mAnimation.getDuration();
+        }
+
+        @Override
+        public long getStartOffset() {
+            return mAnimation.getStartOffset() + mDelay;
+        }
+
+        @Override
+        public int getRepeatMode() {
+            return mAnimation.getRepeatMode();
+        }
+
+        @Override
+        public boolean getFillBefore() {
+            return mAnimation.getFillBefore();
+        }
+
+        @Override
+        public boolean getFillAfter() {
+            return mAnimation.getFillAfter();
+        }
+
+        @Override
+        public boolean willChangeTransformationMatrix() {
+            return mAnimation.willChangeTransformationMatrix();
+        }
+
+        @Override
+        public boolean willChangeBounds() {
+            return mAnimation.willChangeBounds();
+        }
+    }
+}
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..c7a0cc8
--- /dev/null
+++ b/core/java/android/view/animation/Transformation.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 + "}";
+    }
+}
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
new file mode 100644
index 0000000..ae21768
--- /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 whan an ScaleAnimation 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..c358047
--- /dev/null
+++ b/core/java/android/view/animation/package.html
@@ -0,0 +1,244 @@
+<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; frame by frame animation is
+    handled by the {@link android.graphics.drawable.AnimationDrawable} class.
+    Animations do not have a pause method.</p>
+<h2>Tweened Animation<a name="tweened"></a></h2>
+<p> Android can perform simple visual transformations for you, including straight
+    line motion, size change, and transparency change, on the contents of a {@link
+    android.view.View View} object. These transformations are represented by the
+    following classes:</p>
+<ul>
+    <li>    {@link android.view.animation.AlphaAnimation AlphaAnimation} (transparency
+        changes) </li>
+    <li>{@link android.view.animation.RotateAnimation        RotateAnimation} (rotations) </li>
+    <li>        {@link android.view.animation.ScaleAnimation ScaleAnimation} (growing
+        or shrinking) </li>
+    <li>{@link android.view.animation.TranslateAnimation        TranslateAnimation}
+        (position changes) </li>
+</ul>
+<p><em>Note: tweened animation does not provide tools to help you draw shapes.</em> Tweened
+    animation is the act of applying one or more of these 
+    transformations applied to the contents of a View object. So, if you have a TextView
+    with text, you can move, rotate, grow, or shrink the text. If it has a background
+    image, the background image will also be transformed along with the text. </p>
+<p>Animations are drawn in the area designated for the View at the start of the animation;
+    this area does not change to accommodate size or movement, so if your animation
+    moves or expands outside the original boundaries of your object, it will be clipped
+    to the size of the original canvas, even if the object's LayoutParams are
+    set to WRAP_CONTENT (the object will not resize to accommodate moving or expanding/shrinking
+    animations).</p>
+<h3>Step 1: Define your animation </h3>
+<p>The first step in creating a tweened animation is to define the transformations.
+    This can be done either in XML or in code. You define an animation by defining
+    the transformations that you want to occur, when they will occur, and how long
+    they should take to apply. Transformations
+    can be sequential or simultaneous&mdash;so, for example, you can have the contents
+    of a TextView move from left to right, and then rotate 180 degrees, or you can
+    have the text move and rotate simultaneously. Each transformation takes a set
+    of parameters specific for that transformation (starting size and ending size
+    for size change, starting angle and ending angle for rotation, and so on), and
+    also a set of common parameters (for instance, start time and duration). To make
+    several transformations happen simultaneously, give them the same start time;
+    to make them sequential, calculate the start time plus the duration of the preceding
+    transformation. </p>
+<p>Screen coordinates are (0,0) at the upper left hand corner, and increase as you
+    go down and to the right. </p>
+<p>Some values, such as pivotX, can be specified relative to the object itself or
+    relative to the parent. Be sure to use the proper format for what you want (&quot;50&quot;
+    for 50% relative to the parent, &quot;50%&quot; for 50% relative to itself).</p>
+<p>You can determine how a transformation is applied over time by assigning an Interpolator
+    to it. Android includes several Interpolator subclasses that specify various
+    speed curves: for instance, AccelerateInterpolator tells a transformation
+    to start slow and speed up; DecelerateInterpolator tells it to start fast than slow
+    down, and so on. </p>
+<p>If
+    you want a group of transformations to share a set of parameters (for example,
+    start time and duration), you can bundle them into an AnimationSet, which
+    defines the common parameters for all its children (and overrides any
+    values explicitly set by the children). Add your AnimationSet as
+    a child to the root AnimationSet (which serves to wrap all transformations into
+    the final animation). </p>
+<p>    Here is the XML that defines a simple animation. The object will first move
+    to the right, then rotate and double in size, then move up. Note the
+    transformation start times.</p>
+<table width="100%" border="1">
+    <tr>
+        <th scope="col">XML</th>
+        <th scope="col">Equivalent Java </th>
+    </tr>
+    <tr>
+        <td><pre>&lt;set android:shareInterpolator=&quot;true&quot; 
+     android:interpolator=&quot;@android:anim/accelerate_interpolator&quot;&gt;
+
+    &lt;translate android:fromXDelta=&quot;0&quot;
+               android:toXDelta=&quot;30&quot;
+               android:duration=&quot;800&quot;
+               android:fillAfter=&quot;true&quot;/&gt;
+    
+    &lt;set android:duration="800" 
+         android:pivotX=&quot;50%&quot;
+         android:pivotY=&quot;50%&quot; &gt;
+
+        &lt;rotate android:fromDegrees=&quot;0&quot;
+                android:toDegrees=&quot;-90&quot; 
+                android:fillAfter=&quot;true&quot;
+                android:startOffset=&quot;800&quot;/&gt;
+    
+        &lt;scale android:fromXScale=&quot;1.0&quot;
+                android:toXScale=&quot;2.0&quot;
+                android:fromYScale=&quot;1.0&quot;
+                android:toYScale=&quot;2.0&quot;
+                android:startOffset=&quot;800&quot; /&gt;
+    &lt;/set&gt;
+
+    &lt;translate android:toYDelta=&quot;-100&quot;
+               android:fillAfter=&quot;true&quot;
+               android:duration=&quot;800&quot;
+               android:startOffset=&quot;1600&quot;/&gt;
+&lt;/set&gt;</pre></td>
+        <td><pre>// Create root AnimationSet.
+AnimationSet rootSet = new AnimationSet(true);
+rootSet.setInterpolator(new AccelerateInterpolator());
+rootSet.setRepeatMode(Animation.NO_REPEAT);
+
+// Create and add first child, a motion animation.
+TranslateAnimation trans1 = new TranslateAnimation(0, 30, 0, 0);
+trans1.setStartOffset(0);
+trans1.setDuration(800);
+trans1.setFillAfter(true);
+rootSet.addAnimation(trans1);
+
+// Create a rotate and a size animation.
+RotateAnimation rotate = new RotateAnimation(
+       0, 
+       -90, 
+       RotateAnimation.RELATIVE_TO_SELF, 0.5f, 
+       RotateAnimation.RELATIVE_TO_SELF, 0.5f);
+       rotate.setFillAfter(true);
+       rotate.setDuration(800);
+
+ScaleAnimation scale = new ScaleAnimation(
+       1, 2, 1, 2, // From x, to x, from y, to y
+       ScaleAnimation.RELATIVE_TO_SELF, 0.5f, 
+       ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
+       scale.setDuration(800);
+       scale.setFillAfter(true);
+
+// Add rotate and size animations to a new set,
+// then add the set to the root set.
+AnimationSet childSet = new AnimationSet(true);
+childSet.setStartOffset(800);
+childSet.addAnimation(rotate);
+childSet.addAnimation(scale);
+rootSet.addAnimation(childSet);
+
+// Add a final motion animation to the root set.
+TranslateAnimation trans2 = new TranslateAnimation(0, 0, 0, -100);
+trans2.setFillAfter(true);
+trans2.setDuration(800);
+trans2.setStartOffset(1600);
+rootSet.addAnimation(trans2);
+
+// Start the animation.
+animWindow.startAnimation(rootSet);</pre></td>
+    </tr>
+</table>
+<p>&nbsp;</p>
+<p>The following diagram shows the animation drawn from this code: </p>
+<p><img src="{@docRoot}images/tweening_example.png" alt="A tweened animation: move right, turn and grow, move up." ></p>
+<p>The previous diagram shows a few important things. One is the animation itself,
+    and the other is that the animation can get cropped if it moves out of its originally
+    defined area. To avoid this, we could have sized the TextView to fill_parent
+    for its height. </p>
+<p>If you define your animation in XML, save it in the res/anim/ folder as described
+    in <a href="{@docRoot}reference/available-resources.html#tweenedanimation">Resources</a>. That topic
+    also describes the XML tags and attributes you can use to specify transformations. </p>
+<p>Animations
+    have the following common parameters (from the Animation interface).
+    If a group of animations share the same values, you can bundle them into an AnimationSet
+    so you don't have to set these values on each one individually.</p>
+<table width="100%" border="1">
+    <tr>
+        <th scope="col">Property</th>
+        <th scope="col">XML Attribute</th>
+        <th scope="col">Java Method / </th>
+        <th scope="col">Description</th>
+    </tr>
+    <tr>
+        <td>Start time </td>
+        <td><code>android:startOffset</code></td>
+        <td><code>Animation.setStartOffset()</code> (or <code>setStartTime()</code> for absolute time)</td>
+        <td>The start time (in milliseconds) of a transformation, where 0 is the
+        start time of the root animation set. </td>
+    </tr>
+    <tr>
+        <td>Duration</td>
+        <td><code>android:duration</code></td>
+        <td><code>Animation.setDuration()</code></td>
+        <td>The duration (in milliseconds) of a transformation. </td>
+    </tr>
+    <tr>
+        <td>Fill before </td>
+        <td><code>android:fillBefore</code></td>
+        <td><code>Animation.setFillBefore()</code></td>
+        <td>True if you want this transformation to be applied at time zero, regardless
+        of your start time value (you will probably never need this). </td>
+    </tr>
+    <tr>
+        <td>Fill after </td>
+        <td><code>android:fillAfter</code></td>
+        <td><code>Animation.SetFillAfter()</code></td>
+        <td>Whether you want the transform you apply to continue after the duration
+            of the transformation has expired. If false, the original value will
+            immediately be applied when the transformation is done. So, for example,
+            if you want to make a dot move down, then right in an &quot;L&quot; shape, if this
+            value is not true, at the end of the down motion the text box will immediately
+        jump back to the top before moving right. </td>
+    </tr>
+    <tr>
+        <td>Interpolator</td>
+        <td><code>android:interpolator</code></td>
+        <td><code>Animation.SetInterpolator()</code></td>
+        <td>Which interpolator to use. </td>
+    </tr>
+    <tr>
+        <td>Repeat mode </td>
+        <td>Cannot be set in XML </td>
+        <td><code>Animation.SetRepeatMode()</code></td>
+        <td>Whether and how the animation should repeat. </td>
+    </tr>
+</table>
+<p>&nbsp; </p>
+<h3>Step 2: Load and start your animation </h3>
+<ol>
+    <li>If you've created your transformation in XML, you'll need to load it in Java
+        by calling {@link android.view.animation.AnimationUtils#loadAnimation(android.content.Context,
+        int) AnimationUtils.loadAnimation()}. </li>
+    <li>Either start the animation immediately by calling {@link android.view.View#startAnimation(android.view.animation.Animation)
+        View.startAnimation()}, or if you have specified a start time in the animation
+        parameters, you can call 
+        {@link android.view.View#setAnimation(android.view.animation.Animation)
+        View.setCurrentAnimation()}.</li>
+</ol>
+<p>The following code demonstrates loading and starting an animation. </p>
+<pre>// Hook into the object to be animated.
+TextView animWindow = (TextView)findViewById(R.id.anim);
+
+// Load the animation from XML (XML file is res/anim/move_animation.xml).
+Animation anim = AnimationUtils.loadAnimation(AnimationSample.this, R.anim.move_animation);
+anim.setRepeatMode(Animation.NO_REPEAT);
+
+// Play the animation.
+animWindow.startAnimation(anim);</pre>
+</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..e99c444
--- /dev/null
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 junit.framework.Assert;
+
+import java.net.URLDecoder;
+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;
+    private boolean mLoadInitFromJava;
+    private String mCurrentUrl;
+    private int mLoadType;
+    private String mCompletedUrl;
+    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(am, proxy.getBackForwardList());
+        // Create a native FrameView and attach it to the native frame.
+        nativeCreateView(w);
+
+        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 {
+            if (!nativeLoadUrl(url)) {
+                reportError(android.net.http.EventHandler.ERROR_BAD_URL,
+                        mContext.getString(com.android.internal.R.string.httpErrorBadUrl),
+                        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;
+    }
+
+    /**
+     * 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 */String currentUrl() {
+        return mCurrentUrl;
+    }
+
+    /* package */void didFirstLayout(String url) {
+        // this is common case
+        if (url.equals(mCurrentUrl)) {
+            if (!mFirstLayoutDone) {
+                mFirstLayoutDone = true;
+                // ensure {@link WebViewCore#webkitDraw} is called as we were
+                // blocking the update in {@link #loadStarted}
+                mWebViewCore.contentInvalidate();
+            }
+        } else if (url.equals(mCompletedUrl)) {
+            /*
+             * FIXME: when loading http://www.google.com/m, 
+             * mCurrentUrl will be http://www.google.com/m, 
+             * mCompletedUrl will be http://www.google.com/m#search 
+             * and url will be http://www.google.com/m#search. 
+             * This is probably a bug in WebKit. If url matches mCompletedUrl, 
+             * also set mFirstLayoutDone to be true and update.
+             */
+            if (!mFirstLayoutDone) {
+                mFirstLayoutDone = true;
+                // ensure {@link WebViewCore#webkitDraw} is called as we were
+                // blocking the update in {@link #loadStarted}
+                mWebViewCore.contentInvalidate();
+            }
+        }
+        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) {
+            mCurrentUrl = url;
+            mCompletedUrl = null;
+            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) {
+            mCompletedUrl = url;
+            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 host = uri.mHost;
+                    String[] up = mDatabase.getUsernamePassword(host);
+                    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;
+        }
+        return mCallbackProxy.shouldOverrideUrlLoading(url);
+    }
+
+    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.
+     * @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,
+                                              String 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 host = uri.mHost;
+                    String[] ret = getUsernamePassword();
+                    if (ret != null && postData != null && ret[0].length() > 0
+                            && ret[1].length() > 0
+                            && postData.contains(URLEncoder.encode(ret[0]))
+                            && postData.contains(URLEncoder.encode(ret[1]))) {
+                        String[] saved = mDatabase.getUsernamePassword(host);
+                        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(host, ret[0],
+                                        ret[1]);
+                            }
+                        } else {
+                            // CallbackProxy will handle creating the resume
+                            // message
+                            mCallbackProxy.onSavePassword(host, ret[0], ret[1],
+                                    null);
+                        }
+                    }
+                } catch (ParseException ex) {
+                    // if it is bad uri, don't save its password
+                }
+            }
+            if (postData == null) {
+                postData = "";
+            }
+        }
+
+        // is this resource the main-frame top-level page?
+        boolean isMainFramePage = mIsMainFrame && url.equals(mCurrentUrl);
+
+        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.getUserAgentString(), 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();
+    }
+
+    //==========================================================================
+    // native functions
+    //==========================================================================
+
+    /**
+     * Create a new native frame.
+     * @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(AssetManager am,
+            WebBackForwardList list);
+
+    /**
+     * Create a native view attached to a WebView.
+     * @param w A WebView that the frame draws into.
+     */
+    private native void nativeCreateView(WebViewCore w);
+
+    private native void nativeCallPolicyFunction(int policyFunction,
+            int decision);
+    /**
+     * Destroy the native frame.
+     */
+    public native void nativeDestroyFrame();
+
+    /**
+     * Detach the view from the frame.
+     */
+    private native void nativeDetachView();
+
+    /**
+     * 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.
+     */
+    public native void goBackOrForward(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 boolean 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..16d663c
--- /dev/null
+++ b/core/java/android/webkit/ByteArrayBuilder.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.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();
+    }
+
+    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..f5a09b8
--- /dev/null
+++ b/core/java/android/webkit/CacheManager.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+    private static WebViewDatabase mDataBase;
+    private static File mBaseDir;
+
+    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 (!mBaseDir.exists()) {
+            if(!mBaseDir.mkdirs()) {
+                Log.w(LOGTAG, "Unable to create webviewCache directory");
+                return;
+            }
+            FileUtils.setPermissions(
+                    mBaseDir.toString(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                    -1, -1);
+        }
+    }
+
+    /**
+     * 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() {
+        return mDataBase.endCacheTransaction();
+    }
+
+    /**
+     * 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) {
+                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() {
+        // 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();
+                    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..7c296cc
--- /dev/null
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -0,0 +1,906 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ActivityNotFoundException;
+import android.content.Context;
+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 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 host = 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(host, 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) {
+                    JsResult res = (JsResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsAlert(mWebView, url, message,
+                                res)) {
+                        res.handleDefault();
+                    }
+                    res.setReady();
+                }
+                break;
+
+            case JS_CONFIRM:
+                if (mWebChromeClient != null) {
+                    JsResult res = (JsResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsConfirm(mWebView, url, message,
+                                res)) {
+                        res.handleDefault();
+                    }
+                    // Tell the JsResult that it is ready for client
+                    // interaction.
+                    res.setReady();
+                }
+                break;
+
+            case JS_PROMPT:
+                if (mWebChromeClient != null) {
+                    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)) {
+                        res.handleDefault();
+                    }
+                    // Tell the JsResult that it is ready for client
+                    // interaction.
+                    res.setReady();
+                }
+                break;
+
+            case JS_UNLOAD:
+                if (mWebChromeClient != null) {
+                    JsResult res = (JsResult) msg.obj;
+                    String message = msg.getData().getString("message");
+                    String url = msg.getData().getString("url");
+                    if (!mWebChromeClient.onJsBeforeUnload(mWebView, url,
+                                message, res)) {
+                        res.handleDefault();
+                    }
+                    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));
+    }
+
+    //--------------------------------------------------------------------------
+    // 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 host, 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", host);
+        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) {
+        if (Config.DEBUG && mBackForwardList.getCurrentItem() == null) {
+            throw new AssertionError();
+        }
+        mBackForwardList.getCurrentItem().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..176471f
--- /dev/null
+++ b/core/java/android/webkit/CookieManager.java
@@ -0,0 +1,898 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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) {
+            return urlHost.equals(domain) ||
+                    (domain.startsWith(".") &&
+                     urlHost.endsWith(domain.substring(1)));
+        }
+
+        boolean pathMatch(String urlPath) {
+            return urlPath.startsWith (path);
+        }
+
+        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() > 4096) {
+            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;
+        }
+
+        ArrayList<Cookie> cookies = null;
+        try {
+            /* Google is setting cookies like the following to detect whether 
+             * a browser supports cookie. We need to skip the leading "www" for 
+             * the default host. Otherwise the second cookie will make the first
+             * cookie expired.
+             *  
+             * url: https://www.google.com/accounts/ServiceLoginAuth 
+             * value: LSID=xxxxxxxxxxxxx;Path=/accounts;
+             * Expires=Tue, 13-Mar-2018 01:41:39 GMT
+             * 
+             * url: https://www.google.com/accounts/ServiceLoginAuth 
+             * value:LSID=EXPIRED;Domain=www.google.com;Path=/accounts;
+             * Expires=Mon, 01-Jan-1990 00:00:00 GMT 
+             */  
+            if (hostAndPath[0].startsWith("www.")) {
+                hostAndPath[0] = hostAndPath[0].substring(3);
+            }
+            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 synchronized void removeSessionCookie() {
+        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();
+    }
+
+    /**
+     * Remove all cookies
+     */
+    public synchronized void removeAllCookie() {
+        mCookieMap = new LinkedHashMap<String, ArrayList<Cookie>>(
+                MAX_DOMAIN_COUNT, 0.75f, true);
+        CookieSyncManager.getInstance().clearAllCookies();
+    }
+
+    /**
+     *  Return true if there are stored cookies.
+     */
+    public synchronized boolean hasCookies() {
+        return CookieSyncManager.getInstance().hasCookies();
+    }
+
+    /**
+     * Remove all expired cookies
+     */
+    public synchronized void removeExpiredCookie() {
+        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);
+    }
+
+    /**
+     * 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/"
+             * for http://www.google.com/lab?hl=en, the path is "/lab/"
+             * for http://www.google.com/lab.asp?hl=en, the path is "/"
+             * Note: the path from URI has at least one "/"
+             */
+            index = ret[1].indexOf(QUESTION_MARK);
+            if (index != -1) {
+                ret[1] = ret[1].substring(0, index);
+                if (ret[1].charAt(ret[1].length() - 1) != PATH_DELIM) {
+                    index = ret[1].lastIndexOf(PATH_DELIM);
+                    if (ret[1].lastIndexOf('.') > index) {
+                        ret[1] = ret[1].substring(0, index + 1);
+                    } else {
+                        ret[1] += PATH_DELIM;
+                    }
+                }
+            } else if (ret[1].charAt(ret[1].length() - 1) != PATH_DELIM) {
+                ret[1] = ret[1].substring(0,
+                        ret[1].lastIndexOf(PATH_DELIM) + 1);
+            }
+            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>();
+
+        // domain needs at least two PERIOD,
+        if (host.indexOf(PERIOD) == host.lastIndexOf(PERIOD)) {
+            host = PERIOD + host;
+        }
+        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)) {
+                        // make sure path ends with PATH_DELIM
+                        if (value.length() > 1 &&
+                                value.charAt(value.length() - 1) != PATH_DELIM) {
+                            cookie.path = value + PATH_DELIM;
+                        } else {
+                            cookie.path = value;
+                        }
+                    } else if (name.equals(DOMAIN)) {
+                        int lastPeriod = value.lastIndexOf(PERIOD);
+                        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.endsWith(host) || host.endsWith(value)) {
+                            // domain needs at least two PERIOD
+                            if (value.indexOf(PERIOD) == lastPeriod) {
+                                value = PERIOD + value;
+                            }
+                            // disallow cookies set on ccTLDs like [.co.uk]
+                            int len = value.length();
+                            if ((value.charAt(0) == PERIOD)
+                                    && (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 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..3dc15c1
--- /dev/null
+++ b/core/java/android/webkit/DateSorter.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.webkit;
+
+import android.content.Context;
+import android.util.Log;
+
+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];
+
+    Date mDate = new Date();
+    Calendar mCal = Calendar.getInstance();
+
+    /**
+     * @param context Application context
+     */
+    public DateSorter(Context context) {
+
+        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, -4);
+        mBins[2] = c.getTimeInMillis();  // Five days ago
+        c.roll(Calendar.DAY_OF_YEAR, 5); // 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();
+        mLabels[2] = context.getString(com.android.internal.R.string.daysDurationPastPlural, 5);
+        mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
+        StringBuilder sb = new StringBuilder();
+        sb.append(context.getText(com.android.internal.R.string.before)).append(" ");
+        sb.append(context.getText(com.android.internal.R.string.oneMonthDurationPast));
+        mLabels[4] = sb.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..6696bae
--- /dev/null
+++ b/core/java/android/webkit/FileLoader.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.webkit;
+
+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
+
+    /**
+     * 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.
+     */
+    FileLoader(String url, LoadListener loadListener, Context context,
+            boolean asset) {
+        super(loadListener);
+        mIsAsset = asset;
+        mContext = context;
+
+        // 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) {
+                mDataStream = mContext.getAssets().open(mPath,
+                        AssetManager.ACCESS_STREAMING);
+            } else {
+                mHandler.error(EventHandler.FILE_ERROR,
+                        mContext.getString(
+                                com.android.internal.R.string.httpErrorFileNotFound));
+                return false;
+/*
+                if (!mPath.startsWith(
+                        Environment.getExternalStorageDirectory().getPath())) {
+                    mHandler.error(EventHandler.FILE_ERROR,
+                            mContext.getString(
+                                    com.android.internal.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(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;
+        }
+        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.
+     */
+    public static void requestUrl(String url, LoadListener loadListener,
+            Context context, boolean asset) {
+        FileLoader loader = new FileLoader(url, loadListener, context, asset);
+        loader.load();
+    }
+
+}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
new file mode 100644
index 0000000..ebfebd0
--- /dev/null
+++ b/core/java/android/webkit/FrameLoader.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 {
+
+    protected LoadListener mListener;
+    protected Map<String, String> mHeaders;
+    protected String mMethod;
+    protected String mPostData;
+    protected boolean mIsHighPriority;
+    protected Network mNetwork;
+    protected int mCacheMode;
+    protected String mReferrer;
+    protected String mUserAgent;
+    protected 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";
+    
+    /*
+     * Construct the Accept_Language once. If the user changes language, then
+     * the phone will be rebooted.
+     */
+    private static String ACCEPT_LANGUAGE;
+    static {
+        // Set the accept-language to the current locale plus US if we are in a
+        // different locale than US.
+        java.util.Locale l = java.util.Locale.getDefault();
+        ACCEPT_LANGUAGE = "";
+        if (l.getLanguage() != null) {
+            ACCEPT_LANGUAGE += l.getLanguage();
+            if (l.getCountry() != null) {
+                ACCEPT_LANGUAGE += "-" + l.getCountry();
+            }
+        }
+        if (!l.equals(java.util.Locale.US)) {
+            ACCEPT_LANGUAGE += ", ";
+            java.util.Locale us = java.util.Locale.US;
+            if (us.getLanguage() != null) {
+                ACCEPT_LANGUAGE += us.getLanguage();
+                if (us.getCountry() != null) {
+                    ACCEPT_LANGUAGE += "-" + us.getCountry();
+                }
+            }
+        }
+    }
+
+
+    FrameLoader(LoadListener listener, String userAgent,
+            String method, boolean highPriority) {
+        mListener = listener;
+        mHeaders = null;
+        mMethod = method;
+        mIsHighPriority = highPriority;
+        mCacheMode = WebSettings.LOAD_NORMAL;
+        mUserAgent = userAgent;
+    }
+
+    public void setReferrer(String ref) {
+        // only set referrer for http or https
+        if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
+    }
+
+    public void setPostData(String 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)){
+            mNetwork = Network.getInstance(mListener.getContext());
+            return handleHTTPLoad(false);
+        } else if (URLUtil.isCookielessProxyUrl(url)) {
+            mNetwork = Network.getInstance(mListener.getContext());
+            return handleHTTPLoad(true);
+        } else if (handleLocalFile(url, mListener)) {
+            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) {
+        if (URLUtil.isAssetUrl(url)) {
+            FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
+                    true);
+            return true;
+        } else if (URLUtil.isFileUrl(url)) {
+            FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
+                    false);
+            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;
+    }
+    
+    protected boolean handleHTTPLoad(boolean proxyUrl) {
+        if (mHeaders == null) {
+            mHeaders = new HashMap<String, String>();
+        }
+        populateStaticHeaders();
+
+        if (!proxyUrl) {
+            // Don't add private information if this is a proxy load, ie don't
+            // add cookies and authentication
+            populateHeaders();
+        } else {
+            // If this is a proxy URL, fix it to be a network load
+            mListener.setUrl("http://"
+                    + mListener.url().substring(URLUtil.PROXY_BASE.length()));
+        }
+
+        // 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.
+     */
+    protected 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);
+        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.
+     */
+    protected 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
+     */
+    protected 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");
+
+        if (ACCEPT_LANGUAGE.length() > 0) {
+            mHeaders.put("Accept-Language", ACCEPT_LANGUAGE);
+        }
+
+        mHeaders.put("User-Agent", mUserAgent);
+    }
+
+    /**
+     * 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..b22f2ba
--- /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.pim.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..1cfea99
--- /dev/null
+++ b/core/java/android/webkit/JWebCoreJavaBridge.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.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);
+}
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..86947a2
--- /dev/null
+++ b/core/java/android/webkit/LoadListener.java
@@ -0,0 +1,1409 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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;
+
+    // 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 String 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.
+                 */
+                if (mNativeLoader != 0) {
+                    commitHeaders();
+                }
+                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) {
+                    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.
+                 *
+                 */
+                tearDown();
+                break;
+
+            case MSG_CONTENT_ERROR:
+                /*
+                 * This message is sent when a load error has occured. The
+                 * LoadListener will clean itself up.
+                 */
+                notifyError();
+                tearDown();
+                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);
+                //TODO, need to call mCallbackProxy and request UI.
+                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");
+        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") || 
+                    mMimeType.
+                    equalsIgnoreCase("application/vnd.wap.xhtml+xml")) {
+                // 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.
+                if (mMimeType.equalsIgnoreCase("application/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;
+            }
+        }
+        sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS));
+    }
+
+    /**
+     * @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);
+        }
+
+        if (mCancelled) return;
+
+        mStatusCode = code;
+        mStatusText = reasonPhrase;
+        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
+     */
+    public void certificate(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) {
+        mErrorID = id;
+        mErrorDescription = description;
+        sendMessageInternal(obtainMessage(MSG_CONTENT_ERROR));
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.error url:" +
+                    url() + " id:" + id + " description:" + description);
+        }
+        detachRequestHandle();
+    }
+
+    /**
+     * 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
+     */
+    public void data(byte[] data, int length) {
+        if (Config.LOGV) {
+            Log.v(LOGTAG, "LoadListener.data(): url: " + url());
+        }
+
+        if (ignoreCallbacks()) {
+            return;
+        }
+
+        // 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());
+        }
+
+        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:
+                if (mMethod == null && mRequestHandle == null) {
+                    Log.e(LOGTAG, "LoadListener.endData(): method is null!");
+                    Log.e(LOGTAG, "LoadListener.endData(): url = " + url());
+                }
+                // 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));
+                }
+
+                break;
+
+            case HTTP_AUTH:
+            case HTTP_PROXY_AUTH:
+                if (mAuthHeader != null &&
+                        (Network.getInstance(mContext).isValidProxySet() ||
+                         !mAuthHeader.isProxy())) {
+                    Network.getInstance(mContext).handleAuthRequest(this);
+                } else {
+                    final int stringId =
+                            com.android.internal.R.string.httpErrorUnsupportedAuthScheme;
+                    error(EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME,
+                            getContext().getText(stringId).toString());
+                }
+                break;
+                
+            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());
+                    }
+                    break;
+                } // Fall through to default if there is no CacheLoader
+
+            case HTTP_NOT_FOUND:
+                // Not an error, the server can send back content.
+            default:
+                sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
+                detachRequestHandle();
+                break;
+        }
+    }
+
+    /**
+     * 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);
+
+        if (result != null) {
+            CacheLoader cacheLoader =
+                    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
+                cacheLoader.load();
+                return true;
+            } else {
+                // The contents of the cache need to be revalidated
+                // so just provide the listener with the cache loader
+                // in the case that the server response positively to
+                // the cached content.
+                setCacheLoader(cacheLoader);
+            }
+        }
+        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());
+        }
+
+        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);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the CacheLoader for the case where we might want to load from cache
+     * @param result
+     */
+    void setCacheLoader(CacheLoader result) {
+        mCacheLoader = result;
+    }
+    
+    /**
+     * 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, 
+            String 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;
+    }
+
+    private void commitHeaders() {
+        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;
+        }
+
+        // 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
+        int nativeResponse = nativeCreateResponse(mUrl, statusCode, mStatusText,
+                mMimeType, mContentLength, mEncoding,
+                mCacheResult == null ? 0 : mCacheResult.expires / 1000);
+        if (mHeaders != null) {
+            // "content-disposition",
+            String value = mHeaders.getContentDisposition();
+            if (value != null) {
+                nativeSetResponseHeader(nativeResponse, 
+                        Headers.CONTENT_DISPOSITION, value);
+            }
+            
+            // location            
+            value = mHeaders.getLocation();
+            if (value != null) {
+                nativeSetResponseHeader(nativeResponse,
+                        Headers.LOCATION, value);
+            }
+            
+            // refresh (paypal.com are using this)
+            value = mHeaders.getRefresh();
+            if (value != null) {
+                nativeSetResponseHeader(nativeResponse,
+                        Headers.REFRESH, value);
+            }
+
+            // Content-Type
+            value = mHeaders.getContentType();
+            if (value != null) {
+                nativeSetResponseHeader(nativeResponse,
+                        Headers.CONTENT_TYPE, 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();
+    }
+
+    /*
+     * 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;
+        }
+
+        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)) {
+                cancel();
+                final String text = mContext
+                        .getString(com.android.internal.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>();
+            }
+            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 {
+                    String method = mMethod;
+
+                    if (method == null) {
+                        return;
+                    }
+
+                    Network network = Network.getInstance(getContext());
+                    network.requestURL(method, mRequestHeaders,
+                            mPostData, this, mIsHighPriority);
+                }
+            }
+        } else {
+            cancel();
+        }
+
+        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.startsWith(
+                                    HttpAuthHeader.BASIC_TOKEN, i)) {
+                                pos[posLen++] = i;
+                                continue;
+                            }
+
+                            if (header.startsWith(
+                                    HttpAuthHeader.DIGEST_TOKEN, i)) {
+                                pos[posLen++] = i;
+                                continue;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (posLen > 0) {
+                // consider all digest schemes first (if any)
+                for (int i = 0; i < posLen; i++) {
+                    if (header.startsWith(HttpAuthHeader.DIGEST_TOKEN,
+                                pos[i])) {
+                        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.startsWith(HttpAuthHeader.BASIC_TOKEN, pos[i])) {
+                        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) {
+            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(
+                    com.android.internal.R.string.httpErrorBadUrl);
+            error(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..2700aa5
--- /dev/null
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+            sMimeTypeMap.loadEntry("text/plain", "asc", true);
+            sMimeTypeMap.loadEntry("text/plain", "txt", 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/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/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..ea42e58
--- /dev/null
+++ b/core/java/android/webkit/Network.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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,
+                              String 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) {
+            byte[] data = postData.getBytes();
+            bodyLength = data.length;
+            bodyProvider = new ByteArrayInputStream(data);
+        }
+
+        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..95209c7
--- /dev/null
+++ b/core/java/android/webkit/TextDialog.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.os.Handler;
+import android.os.Message;
+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.MetaKeyKeyListener;
+import android.text.method.MovementMethod;
+import android.text.method.PasswordTransformationMethod;
+import android.text.method.TextKeyListener;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View.MeasureSpec;
+import android.view.ViewConfiguration;
+import android.widget.AbsoluteLayout.LayoutParams;
+import android.widget.AutoCompleteTextView;
+
+/**
+ * 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;
+    // Determines whether we allow calls to requestRectangleOnScreen to
+    // propagate.  We only want to scroll if the user is typing.  If the
+    // user is simply navigating through a textfield, we do not want to
+    // scroll.
+    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];
+    // The time of the last enter down, so we know whether to perform a long
+    // press.
+    private long            mDownTime;
+
+    private boolean         mTrackballDown = false;
+    private static int      LONGPRESS = 1;
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (msg.what == LONGPRESS) {
+                if (mTrackballDown) {
+                    performLongClick();
+                    mTrackballDown = false;
+                }
+            }
+        }
+    };
+
+    /**
+     * 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);
+    }
+
+    @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;
+        }
+
+        // For single-line textfields, return key should not be handled
+        // here.  Instead, the WebView is passed the key up, so it may fire a
+        // submit/onClick.
+        // Center key should always be passed to a potential onClick
+        if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)
+                || KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+            if (isPopupShowing()) {
+                super.dispatchKeyEvent(event);
+                return true;
+            }
+            if (down) {
+                if (event.getRepeatCount() == 0) {
+                    mGotEnterDown = true;
+                    mDownTime = event.getEventTime();
+                    // Send the keydown when the up comes, so that we have
+                    // a chance to handle a long press.
+                } else if (mGotEnterDown && event.getEventTime() - mDownTime >
+                        ViewConfiguration.getLongPressTimeout()) {
+                    performLongClick();
+                    mGotEnterDown = false;
+                }
+            } else if (mGotEnterDown) {
+                mGotEnterDown = false;
+                if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+                    mWebView.shortPressOnTextField();
+                    return true;
+                }
+                sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+                sendDomEvent(event);
+            }
+            return true;
+        }
+        // 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_DPAD_CENTER:
+                case KeyEvent.KEYCODE_ENTER:
+                    // For multi-line text boxes, newlines and dpad center 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 or dpad center. This prevents
+        // multiple newlines in the native textarea.
+        if (mGotEnterDown && !down) {
+            return true;
+        }
+        // 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);
+    }
+    
+    /**
+     *  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;
+        }
+        // In this case, replace before with all but the last character of the 
+        // new text.
+        if (count > 1) {
+            String replace = s.subSequence(start, start + count - 1).toString();
+            mWebView.replaceTextfieldText(start, start + before, replace,
+                    start + count - 1, start + count - 1);
+        } else {
+            // This corrects the selection which may have been affected by the 
+            // trackball or auto-correct.
+            mWebView.setSelection(start, start + before);
+        }
+        // Whether the text to be added is only one character, or we already
+        // added all but the last character, we now figure out the DOM events
+        // for the last character, and pass them down.
+        TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0);
+        // We only care about the events that translate directly into 
+        // characters. Should we be using KeyCharacterMap.BUILT_IN_KEYBOARD?
+        // The comment makes it sound like it may not be directly related to 
+        // the keys. However, KeyCharacterMap.ALPHA says it has "maybe some
+        // numbers."  Not sure if that will have the numbers we may need.
+        KeyCharacterMap kmap =
+                KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
+        KeyEvent[] events = kmap.getEvents(mCharacter);
+        updateCachedTextfield();
+        if (null == events) {
+            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);
+        }
+        int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                if (!mTrackballDown) {
+                    mTrackballDown = true;
+                    mHandler.sendEmptyMessageDelayed(LONGPRESS, 
+                            ViewConfiguration.getLongPressTimeout());
+                }
+                return true;
+            case MotionEvent.ACTION_UP:
+                if (mTrackballDown) {
+                    mWebView.shortPressOnTextField();
+                    mTrackballDown = false;
+                    mHandler.removeMessages(LONGPRESS);
+                }
+                return true;
+            case MotionEvent.ACTION_CANCEL:
+                mTrackballDown = false;
+                return true;
+            case MotionEvent.ACTION_MOVE:
+                // fall through
+        }
+        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;
+        }
+        return false;
+    }
+
+    /**
+     * Remove this TextDialog from its host WebView, and return
+     * focus to the host.
+     */
+    /* package */ void remove() {
+        mWebView.removeView(this);
+        mWebView.requestFocus();
+    }
+
+    @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);
+    }
+
+    /**
+     * 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();
+        } 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);
+        mScrollToAccommodateCursor = false;
+        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.
+     */
+    public void setSingleLine(boolean single) {
+        if (mSingle != single) {
+            TextKeyListener.Capitalize cap;
+            if (single) {
+                cap = TextKeyListener.Capitalize.NONE;
+            } else {
+                cap = TextKeyListener.Capitalize.SENTENCES;
+            }
+            setKeyListener(TextKeyListener.getInstance(!single, cap));
+            mSingle = single;
+            setHorizontallyScrolling(single);
+        }
+    }
+
+    /**
+     * 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);
+    }
+    
+    /**
+     *  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..43666c1
--- /dev/null
+++ b/core/java/android/webkit/URLUtil.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     */
+    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..c86b21d
--- /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 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..5570af8
--- /dev/null
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 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 title, Bitmap favicon, byte[] data) {
+        mUrl = url;
+        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..de64b30
--- /dev/null
+++ b/core/java/android/webkit/WebSettings.java
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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 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 = ANDROID_USERAGENT;
+    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         mJavaScriptEnabled = false;
+    private boolean         mPluginsEnabled = false;
+    private boolean         mJavaScriptCanOpenWindowsAutomatically = false;
+    private boolean         mUseDoubleTree = false;
+    private boolean         mUseWideViewport = false;
+    private boolean         mSupportMultipleWindows = 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;
+
+    // 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/522+ " +
+            "(KHTML, like Gecko) Safari/419.3";
+    private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
+            "CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) " +
+            "Version/3.0 Mobile/1A543 Safari/419.3";
+    private static String ANDROID_USERAGENT;
+
+    /**
+     * Package constructor to prevent clients from creating a new settings
+     * instance.
+     */
+    WebSettings(Context context) {
+        if (ANDROID_USERAGENT == null) {
+            StringBuffer arg = new StringBuffer();
+            // Add version
+            final String version = Build.VERSION.RELEASE;
+            if (version.length() > 0) {
+                arg.append(version);
+            } else {
+                // default to "1.0"
+                arg.append("1.0");
+            }
+            arg.append("; ");
+            // Initialize the mobile user agent with the default locale.
+            final Locale l = Locale.getDefault();
+            final String language = l.getLanguage();
+            if (language != null) {
+                arg.append(language.toLowerCase());
+                final String country = l.getCountry();
+                if (country != null) {
+                    arg.append("-");
+                    arg.append(country.toLowerCase());
+                }
+            } else {
+                // default to "en"
+                arg.append("en");
+            }
+            // Add device name
+            final String device = Build.DEVICE;
+            if (device.length() > 0) {
+                arg.append("; ");
+                arg.append(device);
+            }
+            final String base = context.getResources().getText(
+                    com.android.internal.R.string.web_user_agent).toString();
+            ANDROID_USERAGENT = String.format(base, arg);
+            mUserAgent = ANDROID_USERAGENT;
+        }
+        mEventHandler = new EventHandler();
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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) {
+        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.
+     *           2 if the WebView should use an iPhone user-agent string.
+     */
+    public synchronized void setUserAgent(int ua) {
+        if (ua == 0 && !ANDROID_USERAGENT.equals(mUserAgent)) {
+            mUserAgent = ANDROID_USERAGENT;
+            postSync();
+        } else if (ua == 1 && !DESKTOP_USERAGENT.equals(mUserAgent)) {
+            mUserAgent = DESKTOP_USERAGENT;
+            postSync();
+        } else if (ua == 2 && !IPHONE_USERAGENT.equals(mUserAgent)) {
+            mUserAgent = IPHONE_USERAGENT;
+            postSync();
+        }
+    }
+
+    /**
+     * 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.
+     *              2 if the WebView is using an iPhone user-agent string.
+     */
+    public synchronized int getUserAgent() {
+        if (DESKTOP_USERAGENT.equals(mUserAgent)) {
+            return 1;
+        } else if (IPHONE_USERAGENT.equals(mUserAgent)) {
+            return 2;
+        }
+        return 0;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /* Package api to grab the user agent string. */
+    /*package*/ synchronized String getUserAgentString() {
+        return mUserAgent;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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..6623257
--- /dev/null
+++ b/core/java/android/webkit/WebView.java
@@ -0,0 +1,4609 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.text.IClipboard;
+import android.text.Selection;
+import android.text.Spannable;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.animation.AlphaAnimation;
+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.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.ZoomControls;
+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 {
+
+    // 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 RelativeLayout {
+        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 canZoomOut) {
+            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;
+
+    /**
+     * Helper class to get velocity for fling
+     */
+    VelocityTracker mVelocityTracker;
+
+    /**
+     * 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;
+    // 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
+
+    // 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.getTouchSlop()
+    private static final int TOUCH_SLOP_SQUARE =
+            ViewConfiguration.getTouchSlop() * ViewConfiguration.getTouchSlop();
+    // 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.
+    // With a limited survey, the time between the first tap up and the second
+    // tap down in the double tap case is around 70ms - 120ms.
+    private static final int DOUBLE_TAP_TIMEOUT = 200;
+    // 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();
+    // Wait a short time before sending kit focus message, in case 
+    // the user is still moving around, to avoid rebuilding the display list
+    // prematurely 
+    private static final long SET_KIT_FOCUS_DELAY = 250;
+    // 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
+
+    // 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;
+
+    //! 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_TRACKBALL           = 24;
+
+    // width which view is considered to be fully zoomed out
+    static final int ZOOM_OUT_WIDTH = 1024;
+
+    private static final float DEFAULT_MAX_ZOOM_SCALE = 4;
+    private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f;
+    // 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;
+
+    // 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;
+
+    /**
+     * 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;
+        }
+    }
+
+    /**
+     * 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();
+
+        TypedArray a = context.obtainStyledAttributes(
+                com.android.internal.R.styleable.View);
+        initializeScrollbars(a);
+        a.recycle();
+
+        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);
+    }
+
+    private void init() {
+        setWillNotDraw(false);
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setClickable(true);
+        setLongClickable(true);
+
+        // should be conditional on if we're in the browser activity? 
+        setHorizontalScrollBarEnabled(true);
+        setVerticalScrollBarEnabled(true);
+    }
+
+    /* package */ boolean onSavePassword(String host, String username,
+            String password, final Message resumeMsg) {
+       boolean rVal = false;
+       if (resumeMsg == null) {
+           // null resumeMsg implies saving password silently
+           mDatabase.setUsernamePassword(host, username, password);
+       } else {
+            final Message remember = mPrivateHandler.obtainMessage(
+                    REMEMBER_PASSWORD);
+            remember.getData().putString("host", host);
+            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", host);
+            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 (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 (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();
+        getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
+        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();
+    }
+
+    /**
+     * Save the state of this WebView used in Activity.onSaveInstanceState.
+     * @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.
+     */
+    public WebBackForwardList saveState(Bundle outState) {
+        // 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);
+            if (i == currentIndex) {
+                Picture p = capturePicture();
+                String path = mContext.getDir("thumbnails", 0).getPath()
+                        + File.separatorChar + hashCode() + "_pic.save";
+                File f = new File(path);
+                try {
+                    final FileOutputStream out = new FileOutputStream(f);
+                    p.writeToStream(out);
+                    out.close();
+                } catch (FileNotFoundException e){
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                } catch (RuntimeException e) {
+                    e.printStackTrace();
+                }
+                if (f.length() > 0) {
+                    outState.putString("picture", path);
+                    outState.putInt("scrollX", mScrollX);
+                    outState.putInt("scrollY", mScrollY);
+                    outState.putFloat("scale", mActualScale);
+                }
+            }
+        }
+        outState.putSerializable("history", history);
+        if (mCertificate != null) {
+            outState.putBundle("certificate",
+                               SslCertificate.saveState(mCertificate));
+        }
+        return list;
+    }
+
+    /**
+     * Restore the state of this WebView from the given map used in
+     * Activity.onThaw. 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.
+     * @param inState The incoming Bundle of state.
+     * @return The restored back/forward list or null if restoreState failed.
+     */
+    public WebBackForwardList restoreState(Bundle inState) {
+        WebBackForwardList returnList = null;
+        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);
+                }
+                if (inState.containsKey("picture")) {
+                    String path = inState.getString("picture");
+                    File f = new File(path);
+                    if (f.exists()) {
+                        Picture p = null;
+                        try {
+                            final FileInputStream in = new FileInputStream(f);
+                            p = Picture.createFromStream(in);
+                            in.close();
+                            f.delete();
+                        } catch (FileNotFoundException e){
+                            e.printStackTrace();
+                        } catch (RuntimeException e) {
+                            e.printStackTrace();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                        if (p != null) {
+                            int sx = inState.getInt("scrollX", 0);
+                            int sy = inState.getInt("scrollY", 0);
+                            float scale = inState.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();
+                        }
+                    }
+                }
+                // 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.
+     * @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);
+        }
+        // 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) 
+                : 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);
+        }
+        // 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) 
+                : 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.clearContentPicture();
+    }
+    
+    /**
+     * 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(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, the HitTestResult type is set to ANCHOR_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 has to be retrieved through {@link #requestFocusNodeHref}
+     * asynchronously. 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.mIsAnchor && node.mText == null) {
+                result.setType(HitTestResult.ANCHOR_TYPE);
+            } else if (node.mIsTextField || node.mIsTextArea) {
+                result.setType(HitTestResult.EDIT_TEXT_TYPE);
+            } else {
+                String text = node.mText;
+                if (text != null) {
+                    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())));
+                    }
+                }
+            }
+        }
+        int type = result.getType();
+        if (type == HitTestResult.UNKNOWN_TYPE
+                || type == HitTestResult.ANCHOR_TYPE) {
+            // Now check to see if it is an image.
+            int contentX = viewToContent((int) mLastTouchX + mScrollX);
+            int contentY = viewToContent((int) mLastTouchY + mScrollY);
+            if (nativeIsImage(contentX, contentY)) {
+                result.setType(type == HitTestResult.UNKNOWN_TYPE ? 
+                        HitTestResult.IMAGE_TYPE : 
+                        HitTestResult.IMAGE_ANCHOR_TYPE);
+            }
+            if (nativeHasSrcUrl()) {
+                result.setType(result.getType() == HitTestResult.ANCHOR_TYPE ? 
+                        HitTestResult.SRC_ANCHOR_TYPE : 
+                        HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+            }
+        }
+        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) {
+                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) {
+        if (msg == null || mNativeClass == 0) {
+            return;
+        }
+        int contentX = viewToContent((int) mLastTouchX + mScrollX);
+        int contentY = viewToContent((int) mLastTouchY + mScrollY);
+        mWebViewCore.sendMessage(EventHub.REQUEST_IMAGE_HREF, contentX,
+                contentY, msg);
+    }
+
+    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);
+    }
+
+    /* 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) {
+                    mCallbackProxy.onScaleChanged(mActualScale, scale);
+                }
+                mActualScale = scale;
+                mInvActualScale = 1 / scale;
+                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) * getViewWidth() * 0.5f;
+                float sy = ratio * oldY + (ratio - 1) * getViewHeight() * 0.5f;
+
+                // now update our new scale and inverse
+                if (scale != mActualScale) {
+                    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));
+
+                sendViewSizeZoom();
+                sendOurVisibleRect();
+            }
+        }
+    }
+
+    // Used to avoid sending many visible rect messages.
+    private Rect mLastVisibleRectSent;
+
+    private Rect sendOurVisibleRect() {
+        Rect rect = new Rect();
+        calcOurContentVisibleRect(rect);
+        // Rect.equals() checks for null input.
+        if (!rect.equals(mLastVisibleRectSent)) {
+            mWebViewCore.sendMessage(EventHub.SET_VISIBLE_RECT, rect);
+            mLastVisibleRectSent = rect;
+        }
+        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 newWidth = Math.round(getViewWidth() * 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 Float(mActualScale));
+            mLastWidthSent = newWidth;
+            mLastHeightSent = newHeight;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected int computeHorizontalScrollRange() {
+        if (mDrawHistory) {
+            return mHistoryWidth;
+        } else {
+            return contentToView(mContentWidth);
+        }
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        if (mDrawHistory) {
+            return mHistoryHeight;
+        } else {
+            return contentToView(mContentHeight);
+        }
+    }
+
+    /**
+     * 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 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()) {
+            ArrayAdapter<String> adapter = null;
+            mTextEntry.setAdapter(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();
+    }
+
+    /*
+     * Making the find methods private since we are disabling for 1.0
+     *
+     * Find and highlight the next occurance of the String find, beginning with
+     * the current selection. Wraps the page infinitely, and scrolls. When
+     * WebCore determines whether it has found it, the response message is sent
+     * to its target with true(1) or false(0) as arg1 depending on whether the
+     * text was found.
+     * @param find String to find.
+     * @param response A Message object that will be dispatched with the result
+     *                 as the arg1 member. A result of 1 means the search
+     *                 succeeded.
+     */
+    private void findNext(String find, Message response) {
+        if (response == null) {
+            return;
+        }
+        Message m = Message.obtain(null, EventHub.FIND, 1, 0, response);
+        m.getData().putString("find", find);
+        mWebViewCore.sendMessage(m);
+    }
+
+    /*
+     * Making the find methods private since we are disabling for 1.0
+     *
+     * Find and highlight the previous occurance of the String find, beginning
+     * with the current selection.
+     * @param find String to find.
+     * @param response A Message object that will be dispatched with the result
+     *                 as the arg1 member. A result of 1 means the search
+     *                 succeeded.
+     */
+    private void findPrevious(String find, Message response) {
+        if (response == null) {
+            return;
+        }
+        Message m = Message.obtain(null, EventHub.FIND, -1, 0, response);
+        m.getData().putString("find", find);
+        mWebViewCore.sendMessage(m);
+    }
+
+    /*
+     * Making the find methods private since we are disabling for 1.0
+     *
+     * Find and highlight the first occurance of find, beginning with the start
+     * of the page.
+     * @param find String to find.
+     * @param response A Message object that will be dispatched with the result
+     *                 as the arg1 member. A result of 1 means the search
+     *                 succeeded.
+     */
+    private void findFirst(String find, Message response) {
+        if (response == null) {
+            return;
+        }
+        Message m = Message.obtain(null, EventHub.FIND, 0, 0, response);
+        m.getData().putString("find", find);
+        mWebViewCore.sendMessage(m);
+    }
+
+    /*
+     * Making the find methods private since we are disabling for 1.0
+     *
+     * Find all instances of find on the page and highlight them.
+     * @param find  String to find.
+     * @param response A Message object that will be dispatched with the result
+     *                 as the arg1 member.  The result will be the number of
+     *                 matches to the String find.
+     */
+    private void findAll(String find, Message response) {
+        if (response == null) {
+            return;
+        }
+        Message m = Message.obtain(null, EventHub.FIND_ALL, 0, 0, response);
+        m.getData().putString("find", find);
+        mWebViewCore.sendMessage(m);
+    }
+    
+    /**
+     * 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);
+    }
+
+    /*
+     * Making the find methods private since we are disabling for 1.0
+     *
+     * Clear the highlighting surrounding text matches created by findAll.
+     */
+    private void clearMatches() {
+        mWebViewCore.sendMessage(EventHub.CLEAR_MATCHES);
+    }
+
+    /**
+     * 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) {
+        return pinScrollTo(mScrollX + dx, mScrollY + dy, animate);
+    }
+
+    // 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) {
+        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,
+                                  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) {
+        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, true);
+            }
+        } else {
+            pinScrollBy(cx, cy, true);
+        }
+    }
+
+    // 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);
+        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);
+    }
+
+    /**
+     * 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.
+     * IMPORTANT, the object that is bound runs in another thread and
+     * not in the thread that it was constructed in.
+     * @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.
+            nativeRecordButtons();
+            drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
+        }
+        canvas.restoreToCount(sc);
+    }
+
+    @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;
+            }
+            float scale = (mActualScale - zoomScale) * mInvActualScale;
+            float tx = scale * ((getLeft() + getRight()) * 0.5f + mScrollX);
+            float ty = scale * ((getTop() + getBottom()) * 0.5f + 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) {
+            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);
+        }
+    }
+    
+    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 == width) {
+            float offsetX = (width * halfScale - width) / 2;
+            scrollFrame.left -= offsetX;
+            scrollFrame.right += offsetX;
+        }
+        if (mContentHeight == height) {
+            float offsetY = (height * halfScale - height) / 2;
+            scrollFrame.top -= offsetY;
+            scrollFrame.bottom += offsetY;
+        }
+        return scrollFrame;
+    }
+    
+    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 = maxX > 0 ? (float) mZoomScrollX / maxX : 0.5f;
+        float halfY = maxY > 0 ? (float) mZoomScrollY / maxY : 0.5f;
+        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) 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(0, Math.min(maxScreenX, x));
+            mZoomScrollX = (int) (x * maxZoomX / maxScreenX);
+        }
+        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(0, Math.min(maxScreenY, y));
+            mZoomScrollY = (int) (y * maxZoomY / maxScreenY);
+        }
+        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));
+    }
+
+    // Used to register the global focus change listener one time to avoid
+    // multiple references to WebView
+    private boolean mGlobalFocusChangeListenerAdded;
+    
+    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)) {
+            if (alreadyThere) {
+                mTextEntry.remove();
+            }
+            // 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);
+            ArrayAdapter<String> adapter = null;
+            mTextEntry.setAdapter(adapter);
+            mTextEntry.setInPassword(node.mIsPassword);
+            mTextEntry.setSingleLine(node.mIsTextField);
+            if (null == text) {
+                mTextEntry.setText("", 0, 0);
+            } else {
+                mTextEntry.setText(text, 0, text.length());
+            }
+            mTextEntry.requestFocus();
+        }
+        if (!mGlobalFocusChangeListenerAdded) {
+            getViewTreeObserver().addOnGlobalFocusChangeListener(this);
+            mGlobalFocusChangeListenerAdded = true;
+        }
+    }
+
+    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) {
+                ArrayAdapter<String> adapter = new ArrayAdapter<String>(
+                        mContext, com.android.internal.R.layout.simple_list_item_1,
+                        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);
+    }
+
+    // These variables are used to determine long press with the enter key, or
+    // a center key.  Does not affect long press with the trackball/touch.
+    private long mDownTime = 0;
+    private boolean mGotDown = false;
+
+    // Enable copy/paste with trackball here.
+    // This should be left disabled until the framework can guarantee
+    // delivering matching key-up and key-down events for the shift key
+    private static final boolean ENABLE_COPY_PASTE = false;
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
+            return false;
+        }
+        if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER && 
+                keyCode != KeyEvent.KEYCODE_ENTER) {
+            mGotDown = false;
+        }
+        if (mAltIsPressed == false && (keyCode == KeyEvent.KEYCODE_ALT_LEFT 
+                || keyCode == KeyEvent.KEYCODE_ALT_RIGHT)) {
+            mAltIsPressed = true;
+        }
+        if (ENABLE_COPY_PASTE && mShiftIsPressed == false 
+                && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 
+                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
+            mExtendSelection = false;
+            mShiftIsPressed = true;
+            if (mNativeClass != 0 && nativeUpdateFocusNode()) {
+                FocusNode node = mFocusNode;
+                mSelectX = node.mBounds.left;
+                mSelectY = node.mBounds.top;
+            } else {
+                mSelectX = mScrollX + SELECT_CURSOR_OFFSET;
+                mSelectY = mScrollY + SELECT_CURSOR_OFFSET;
+            }
+       }
+       if (keyCode == KeyEvent.KEYCODE_CALL) {
+            if (mNativeClass != 0 && 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;
+                }
+            }
+            return false;
+        }
+
+        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
+            return false;
+        }
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
+                    + ", " + event);
+        }
+        boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP ||
+                keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
+                keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
+                keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
+        if (isArrowKey && event.getEventTime() - mTrackballLastTime 
+                <= TRACKBALL_KEY_TIMEOUT) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "ignore arrow");
+            return false;
+        }
+        boolean weHandledTheKey = false;
+
+        if (event.getMetaState() == 0) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_UP:
+                case KeyEvent.KEYCODE_DPAD_DOWN:
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    // TODO: alternatively we can do panning as touch does
+                    switchOutDrawHistory();
+                    weHandledTheKey = navHandledKey(keyCode, 1, false
+                            , event.getEventTime());
+                    if (weHandledTheKey) {
+                        playSoundEffect(keyCodeToSoundsEffect(keyCode));
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                case KeyEvent.KEYCODE_ENTER:
+                    if (event.getRepeatCount() == 0) {
+                        switchOutDrawHistory();
+                        mDownTime = event.getEventTime();
+                        mGotDown = true;
+                        return true;
+                    } 
+                    if (mGotDown && event.getEventTime() - mDownTime > 
+                            ViewConfiguration.getLongPressTimeout()) {
+                        performLongClick();
+                        mGotDown = false;
+                        return true;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_9:
+                    if (mNativeClass != 0 && getSettings().getNavDump()) {
+                        debugDump();
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        // suppress sending arrow keys to webkit
+        if (!weHandledTheKey && !isArrowKey) {
+            mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, 0, event);
+        }
+
+        return weHandledTheKey;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 
+                || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+            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);
+                }
+            }
+            mShiftIsPressed = false;
+            return true;
+        }
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "MT keyUp at" + System.currentTimeMillis()
+                    + ", " + event);
+        }
+        if (keyCode == KeyEvent.KEYCODE_ALT_LEFT 
+                || keyCode == KeyEvent.KEYCODE_ALT_RIGHT) {
+            mAltIsPressed = false;
+        }
+        if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
+            return false;
+        }
+
+        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;
+        }
+        Rect visibleRect = sendOurVisibleRect();
+        // Note that sendOurVisibleRect calls viewToContent, so the coordinates
+        // should be in content coordinates.
+        boolean nodeOnScreen = false;
+        boolean isTextField = false;
+        boolean isTextArea = false;
+        FocusNode node = null;
+        if (mNativeClass != 0 && nativeUpdateFocusNode()) {
+            node = mFocusNode;
+            isTextField = node.mIsTextField;
+            isTextArea = node.mIsTextArea;
+            nodeOnScreen = Rect.intersects(node.mBounds, visibleRect);
+        }
+
+        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;
+            } else {
+                keyCode = KeyEvent.KEYCODE_ENTER;
+            }
+        }
+        if (KeyEvent.KEYCODE_ENTER == keyCode) {
+            if (LOGV_ENABLED) Log.v(LOGTAG, "KEYCODE_ENTER == keyCode");
+            if (!nodeOnScreen) {
+                return false;
+            }
+            if (node != null && !isTextField && !isTextArea) {
+                nativeSetFollowedLink(true);
+                mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, 
+                        EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0,
+                        new WebViewCore.FocusData(mFocusData));
+                if (mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
+                    return true;
+                }
+                playSoundEffect(SoundEffectConstants.CLICK);
+            }
+        }
+        if (!nodeOnScreen) {
+            // FIXME: Want to give Callback a chance to handle it, and
+            // possibly pass down to javascript.
+            return false;
+        }
+        if (LOGV_ENABLED) Log.v(LOGTAG, "onKeyUp send EventHub.KEY_UP");
+        mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, 0, event);
+        return true;
+    }
+    
+    // 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);
+        }
+    }
+    
+    // Implementation for OnHierarchyChangeListener
+    public void onChildViewAdded(View parent, View child) {}
+    
+    // When we are removed, remove this as a global focus change listener.
+    public void onChildViewRemoved(View p, View child) {
+        if (child == this) {
+            p.getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
+            mGlobalFocusChangeListenerAdded = false;
+        }
+    }
+    
+    // Use this to know when the textview has lost focus, and something other
+    // than the webview has gained focus.  Stop drawing the focus ring, remove
+    // the TextView, and set a flag to put it back when we regain focus.
+    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+        if (oldFocus == mTextEntry && newFocus != this) {
+            mDrawFocusRing = false;
+            mTextEntry.updateCachedTextfield();
+            removeView(mTextEntry);
+            mNeedsUpdateTextEntry = true;
+        }
+    }
+
+    // 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();
+                }
+            } else {
+                // If our window gained focus, but we do not have it, do not
+                // draw the focus ring.
+                mDrawFocusRing = false;
+            }
+        } else {
+            // If our window has lost focus, stop drawing the focus ring, and
+            // remove the TextView if displayed, and flag it to be added when
+            // we regain focus.
+            mDrawFocusRing = false;
+            mGotKeyDown = false;
+            if (inEditingMode()) {
+                clearTextEntry();
+                mNeedsUpdateTextEntry = 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;
+                }
+            }
+        } 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;
+            }
+            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);
+
+        // 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 static final float MAX_SLOPE_FOR_DIAG = 1.5f;
+    private static final int MIN_LOCK_SNAP_REVERSE_DISTANCE = 
+            ViewConfiguration.getTouchSlop();
+    private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mNativeClass == 0 || !isClickable() || !isLongClickable() || 
+                !hasFocus()) {
+            return false;
+        }
+
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
+                    + mTouchMode);
+        }
+
+        int action = ev.getAction();
+        float x = ev.getX();
+        float y = ev.getY();
+        long eventTime = ev.getEventTime();
+        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 {
+                    mTouchMode = TOUCH_INIT_MODE;
+                }
+                if (mTouchMode == TOUCH_INIT_MODE) {
+                    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);
+
+                int deltaX = (int) (mLastTouchX - x);
+                int deltaY = (int) (mLastTouchY - y);
+
+                if (mTouchMode != TOUCH_DRAG_MODE) {
+                    if ((deltaX * deltaX + deltaY * deltaY)
+                            < TOUCH_SLOP_SQUARE) {
+                        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 < -MIN_LOCK_SNAP_REVERSE_DISTANCE)
+                                    || (!mSnapPositive &&
+                                    deltaX > MIN_LOCK_SNAP_REVERSE_DISTANCE))) {
+                                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 < -MIN_LOCK_SNAP_REVERSE_DISTANCE)
+                                    || (!mSnapPositive && 
+                                    deltaY > MIN_LOCK_SNAP_REVERSE_DISTANCE))) {
+                                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 (mZoomControls != null && mMinZoomScale < mMaxZoomScale) {
+                    if (mZoomControls.getVisibility() == View.VISIBLE) {
+                        mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                    } else {
+                        mZoomControls.show(canZoomScrollOut());
+                    }
+                    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: {
+                switch (mTouchMode) {
+                    case TOUCH_INIT_MODE: // tap
+                        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+                        if (getSettings().supportZoom()) {
+                            mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                                    .obtainMessage(RELEASE_SINGLE_TAP),
+                                    DOUBLE_TAP_TIMEOUT);
+                        } else {
+                            // do short press now
+                            mTouchMode = TOUCH_DONE_MODE;
+                            doShortPress();
+                        }
+                        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),
+                                    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 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_MULTIPLIER = 3;
+    private static final int SELECT_CURSOR_OFFSET = 16;
+    private int mSelectX = 0;
+    private int mSelectY = 0;
+    private boolean mShiftIsPressed = false;
+    private boolean mAltIsPressed = false;
+    private boolean mTrackballDown = false;
+    private long mTrackballUpTime = 0;
+    private long mLastFocusTime = 0;
+    private Rect mLastFocusBounds;
+    
+    // Used to determine that the trackball is down AND that it has not
+    // been moved while down, whereas mTrackballDown is true until we
+    // receive an ACTION_UP
+    private boolean mTrackTrackball = false;
+    
+    // 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 (mAltIsPressed) {
+            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);
+            mPrivateHandler.sendMessageDelayed(
+                    mPrivateHandler.obtainMessage(LONG_PRESS_TRACKBALL), 1000);
+            mTrackTrackball = true;
+            mTrackballDown = 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);
+            }
+            return false; // let common code in onKeyDown at it
+        } else if (mTrackTrackball) {
+            mPrivateHandler.removeMessages(LONG_PRESS_TRACKBALL);
+            mTrackTrackball = false;
+        } 
+        if (ev.getAction() == MotionEvent.ACTION_UP) {
+            mTrackballDown = false;
+            mTrackballUpTime = time;
+            if (mShiftIsPressed) {
+                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);
+        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;
+            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);
+            }
+            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;
+    }
+
+    /**
+     * 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) {
+                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.getTouchSlop();
+            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.getTouchSlop();
+        nativeMotionUp(contentX, contentY, contentSize, true);
+
+        // call uiOverride next to check whether it is a special node,
+        // phone/email/address, which are not handled by WebKit
+        if (nativeUpdateFocusNode()) {
+            FocusNode node = mFocusNode;
+            if (!node.mIsTextField && !node.mIsTextArea) {
+                if (mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
+                    return;
+                }
+            }
+            playSoundEffect(SoundEffectConstants.CLICK);
+        }
+    }
+
+    @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;
+                }
+            }
+        }
+        if (mNativeClass != 0) {
+            nativeSetHeightCanMeasure(mHeightCanMeasure);
+        }
+        // For the width, always use the given size unless unspecified.
+        if (widthMode == MeasureSpec.UNSPECIFIED) {
+            mWidthCanMeasure = true;
+            measuredWidth = contentWidth;
+        }
+
+        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);
+        }
+
+        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) {
+            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;
+                    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);
+                    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 Point viewSize = (Point) msg.obj;
+                    if (mZoomScale > 0) {
+                        if (Math.abs(mZoomScale * viewSize.x -
+                                getViewWidth()) < 1) {
+                            mZoomScale = 0;
+                            mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR,
+                                    0, 0);
+                        }
+                    }
+                    // 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(msg.arg1, msg.arg2, updateLayout);
+                    invalidate();
+                    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;
+                    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;
+                        }
+                        // by default starting a new page with 100% zoom scale.
+                        scale = initialScale == 0 ? (viewportWidth > 0 ? 
+                                ((float) width / viewportWidth) : 1.0f)
+                                : 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:
+                    updateTextEntry();
+                    break;
+                case RECOMPUTE_FOCUS_MSG_ID:
+                    if (mNativeClass != 0) {
+                        nativeRecomputeFocus();
+                    }
+                    break;
+                case UPDATE_TEXT_ENTRY_ADAPTER:
+                    HashMap data = (HashMap) msg.obj;
+                    if (mTextEntry.isSameTextField(msg.arg1)) {
+                        ArrayAdapter<String> adapter =
+                                (ArrayAdapter<String>) data.get("adapter");
+                        mTextEntry.setAdapter(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_TRACKBALL:
+                    mTrackTrackball = false;
+                    mTrackballDown = false;
+                    performLongClick();
+                    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);
+                }
+            }
+            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.sendMessageDelayed(EventHub.SET_KIT_FOCUS, focusData,
+                SET_KIT_FOCUS_DELAY);
+    }
+
+    // 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 = new Rect(contentToView(contentFocus.left)
+                , contentToView(contentFocus.top)
+                , contentToView(contentFocus.right)
+                , contentToView(contentFocus.bottom));
+        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);
+        } else {
+            maxH = Math.max(viewFocus.left - visRect.left, -maxXScroll);
+            if (maxH < 0) {
+                pinScrollBy(maxH, 0, true);
+            }
+        }
+        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_WEBKIT);
+    }
+    
+    /**
+     *  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 boolean  nativeUpdateFocusNode();
+    private native Rect     nativeGetFocusRingBounds();
+    private native Rect     nativeGetNavBounds();
+    private native void     nativeMarkNodeInvalid(int node);
+    private native void     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();
+    private native void     nativeRecordButtons();
+    private native void     nativeResetFocus();
+    private native void     nativeResetNavClipBounds();
+    private native void     nativeSelectBestAt(Rect rect);
+    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 boolean  nativeHasSrcUrl();
+    private native boolean  nativeIsImage(int x, int y);
+    private native void     nativeMoveSelection(int x, int y
+            , boolean extendSelection);
+    private native Region   nativeGetSelection();
+}
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..263346e
--- /dev/null
+++ b/core/java/android/webkit/WebViewCore.java
@@ -0,0 +1,1553 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Bundle;
+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;
+
+
+    /*  This is a ring of pictures for content. After B is built, it is swapped
+        with A.
+    */
+    private Picture mContentPictureA = new Picture();   // draw()
+    private Picture mContentPictureB = new Picture();   // nativeDraw()
+
+    /*
+     * 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. */
+    /* 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);
+
+    /**
+     * Find and highlight an occurance of text matching find
+     * @param find The text to find.
+     * @param forward If true, search forward.  Else, search backwards.
+     * @param fromSelection Whether to start from the current selection or from
+     *                      the beginning of the viewable page.
+     * @return boolean Whether the text was found.
+     */
+    private native boolean nativeFind(String find,
+                                      boolean forward,
+                                      boolean fromSelection);
+
+    /**
+     * Find all occurances of text matching find and highlight them.
+     * @param find The text to find.
+     * @return int The number of occurances of find found.
+     */
+    private native int nativeFindAll(String find);
+    
+    /**
+     * Clear highlights on text created by nativeFindAll.
+     */
+    private native void nativeClearMatches();
+
+    private native void nativeDraw(Picture content);
+
+    private native boolean nativeKeyUp(int keycode, int keyvalue);
+
+    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);
+
+    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, int node, int x,
+            int y);
+
+    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);
+    
+    /**
+     *  Return the url of the image located at (x,y) in content coordinates, or
+     *  null if there is no image at that point.
+     *
+     *  @param x    x content ordinate
+     *  @param y    y content ordinate
+     *  @return String  url of the image located at (x,y), or null if there is
+     *                  no image there.
+     */
+    private native String nativeRetrieveImageRef(int x, int y);
+
+    private native void nativeTouchUp(int touchGeneration, 
+            int buildGeneration, int framePtr, int nodePtr, int x, int y, 
+            int size, boolean isClick, boolean retry);
+
+    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 nativeDump();
+
+    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);
+    
+    // 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;
+                                synchronized (core) {
+                                    core.initialize();
+                                    core.notify();
+                                }
+                                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;
+    }
+
+    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_VISIBLE_RECT = 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 FIND = 116;
+        static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117;
+        static final int FIND_ALL = 118;
+        static final int CLEAR_MATCHES = 119;
+        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 DUMP_WEBKIT = 125;
+        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;
+
+        // UI nav messages
+        static final int REQUEST_IMAGE_HREF = 134;
+        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;
+
+        // 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;
+        // 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) {
+                    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;
+                            mBrowserFrame.loadData(
+                                    (String) loadParams.get("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(mBrowserFrame
+                                        .currentUrl());
+                            }
+                            // Do this after syncing up the layout state.
+                            stopLoading();
+                            break;
+
+                        case RELOAD:
+                            mBrowserFrame.reload(false);
+                            break;
+
+                        case KEY_DOWN:
+                            keyDown(msg.arg1, (KeyEvent) msg.obj);
+                            break;
+
+                        case KEY_UP:
+                            keyUp(msg.arg1, (KeyEvent) msg.obj);
+                            break;
+
+                        case VIEW_SIZE_CHANGED:
+                            viewSizeChanged(msg.arg1, msg.arg2,
+                                    ((Float) msg.obj).floatValue());
+                            break;
+
+                        case SET_VISIBLE_RECT:
+                            Rect r = (Rect) msg.obj;
+                            // note: these are in document coordinates
+                            // (inv-zoom)
+                            nativeSetVisibleRect(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, fDat.mNode,
+                                    fDat.mX, fDat.mY);
+                            break;
+                        }
+
+                        case FIND:
+                            /* arg1:
+                             *   1 - Find next
+                             *  -1 - Find previous
+                             *   0 - Find first
+                             */
+                            Message response = (Message) msg.obj;
+                            boolean find = nativeFind(msg.getData().getString("find"),
+                                    msg.arg1 != -1, msg.arg1 != 0);
+                            response.arg1 = find ? 1 : 0;
+                            response.sendToTarget();
+                            break;
+                            
+                        case FIND_ALL:
+                            int found = nativeFindAll(msg.getData().getString("find"));
+                            Message resAll = (Message) msg.obj;
+                            resAll.arg1 = found;
+                            resAll.sendToTarget();
+                            break;
+                            
+                        case CLEAR_MATCHES:
+                            nativeClearMatches();
+                            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 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);
+                            Bundle data = hrefMsg.getData();
+                            data.putString("url", res);
+                            hrefMsg.setData(data);
+                            hrefMsg.sendToTarget();
+                            break;
+                        }
+                            
+                        case REQUEST_IMAGE_HREF: {
+                            Message refMsg = (Message) msg.obj;
+                            String ref = 
+                                    nativeRetrieveImageRef(msg.arg1, msg.arg2);
+                            Bundle data = refMsg.getData();
+                            data.putString("url", ref);
+                            refMsg.setData(data);
+                            refMsg.sendToTarget();
+                            break;
+                        }
+
+                        case UPDATE_CACHE_AND_TEXT_ENTRY:
+                            nativeUpdateFrameCache();
+                            sendViewInvalidate();
+                            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_WEBKIT:
+                            nativeDump();
+                            break;
+
+                        case SYNC_SCROLL:
+                            mWebkitScrollX = msg.arg1;
+                            mWebkitScrollY = msg.arg2;
+                            break;
+
+                        case REFRESH_PLUGINS:
+                            nativeRefreshPlugins(msg.arg1 != 0);
+                            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;
+            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)
+    //-------------------------------------------------------------------------
+
+    public 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 keyDown(int code, KeyEvent event) {
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "CORE keyDown at " + System.currentTimeMillis()
+                    + ", " + event);
+        }
+        mCallbackProxy.onUnhandledKeyEvent(event);
+    }
+
+    private void keyUp(int code, KeyEvent event) {
+        if (LOGV_ENABLED) {
+            Log.v(LOGTAG, "CORE keyUp at " + System.currentTimeMillis()
+                    + ", " + event);
+        }
+        if (!nativeKeyUp(code, event.getUnicodeChar())) {
+            mCallbackProxy.onUnhandledKeyEvent(event);
+        }
+    }
+
+    // These values are used to avoid requesting a layout based on old values
+    private int mCurrentViewWidth = 0;
+    private int mCurrentViewHeight = 0;
+
+    // notify webkit that our virtual view size changed size (after inv-zoom)
+    private void viewSizeChanged(int w, int h, float scale) {
+        if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged");
+        if (mSettings.getUseWideViewPort()
+                && (w < mViewportWidth || mViewportWidth == -1)) {
+            int width = mViewportWidth;
+            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.
+                     */
+                    width = Math.max(w, nativeGetContentMinPrefWidth());
+                }
+            }
+            nativeSetSize(width, Math.round((float) width * h / w), w, scale);
+        } else {
+            nativeSetSize(w, h, w, scale);
+        }
+        // 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 #contentInvalidate} when mCurrentViewWidth is 0
+            contentInvalidate();
+        }
+        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 end scale+scroll mode, accessed by both threads
+    boolean mEndScaleZoom = false;
+    
+    private void webkitDraw() {
+        mDrawIsScheduled = false;
+        nativeDraw(mContentPictureB);
+        int w;
+        int h;
+        synchronized (this) {
+            Picture temp = mContentPictureB;
+            mContentPictureB = mContentPictureA;
+            mContentPictureA = temp;
+            w = mContentPictureA.getWidth();
+            h = mContentPictureA.getHeight();
+        }
+
+        if (mWebView != null) {
+            // Send the native view size that was used during the most recent
+            // layout.
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.NEW_PICTURE_MSG_ID, w, h,
+                    new Point(mCurrentViewWidth, mCurrentViewHeight))
+                    .sendToTarget();
+            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);
+        synchronized (this) {
+            Picture picture = mContentPictureA;
+            int sc = canvas.save(Canvas.CLIP_SAVE_FLAG);
+            Rect clip = new Rect(0, 0, picture.getWidth(), picture.getHeight());
+            canvas.clipRect(clip, Region.Op.DIFFERENCE);
+            canvas.drawColor(color);
+            canvas.restoreToCount(sc);
+    // experiment commented out
+    //      if (TEST_BUCKET) {
+    //          nativeDrawContentPicture(canvas);
+    //      } else {
+                canvas.drawPicture(picture);
+    //      }
+        }
+        canvas.setDrawFilter(null);
+    }
+
+    /* package */ void clearContentPicture() {
+    // experiment commented out
+    //   if (TEST_BUCKET) {
+    //        nativeClearContentPicture();
+    //    }
+        synchronized (this) {
+            mContentPictureA = new Picture();
+        }
+    }
+
+    /*package*/ Picture copyContentPicture() {
+        synchronized (this) {
+            return new Picture(mContentPictureA);
+        }
+    }
+
+    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.mDrawIsScheduled = 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.contentInvalidate();
+            }
+        }
+    }
+
+    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);
+        }
+        list.restoreIndex(mBrowserFrame.mNativeFrame, index);
+    }
+
+    //-------------------------------------------------------------------------
+    // Implement abstract methods in WebViewCore, native WebKit callback part
+    //-------------------------------------------------------------------------
+
+    // called from JNI or WebView thread
+    /* package */ void contentInvalidate() {
+        // 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 (mDrawIsScheduled) {
+                return;
+            }
+            mDrawIsScheduled = true;
+            mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
+        }
+    }
+
+    // called by JNI
+    private void contentScrollBy(int dx, int dy) {
+        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).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));
+    }
+
+    // called by JNI
+    private void sendRecomputeFocus() {
+        if (mWebView != null) {
+            Message.obtain(mWebView.mPrivateHandler,
+                    WebView.RECOMPUTE_FOCUS_MSG_ID).sendToTarget();
+        }
+    }
+
+    // called by JNI
+    private void sendViewInvalidate() {
+        if (mWebView != null) {
+            mWebView.postInvalidate();
+        }
+    }
+
+    /* package */ WebView getWebView() {
+        return mWebView;
+    }
+
+    private native void setViewportSettingsFromNative();
+    
+    // called by JNI
+    private void didFirstLayout(String url) {
+        // 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 scale to indicate that WebCore should reuse the
+            // current scale
+            mEventHub.sendMessage(Message.obtain(null,
+                    EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent,
+                    mWebView.mLastHeightSent, -1.0f));
+        }
+
+        mBrowserFrame.didFirstLayout(url);
+
+        // 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 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 nativeSetVisibleRect(int x, int y, int width,
+            int height);
+
+    // 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..b367e27
--- /dev/null
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -0,0 +1,962 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 = 8;
+    // 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
+    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();
+                    bootstrapDatabase();
+                    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();
+                }
+            }
+
+            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 all old data");
+        }
+        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_PASSWORD_ID]);
+        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.setVersion(DATABASE_VERSION);
+    }
+
+    private static void bootstrapDatabase() {
+        if (mDatabase != null) {
+            // 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)");
+
+            // password
+            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);");
+
+            // 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);");
+        }
+    }
+
+    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
+     */
+    @SuppressWarnings("deprecation")
+    CacheResult getCache(String url) {
+        if (url == null || mCacheDatabase == null) {
+            return null;
+        }
+
+        CacheResult ret = null;
+        final String s = "SELECT filepath, lastmodify, etag, expires, mimetype, encoding, httpstatus, location, contentlength FROM cache WHERE url = ";
+        StringBuilder sb = new StringBuilder(256);
+        sb.append(s);
+        DatabaseUtils.appendEscapedSQLString(sb, url);
+        Cursor cursor = mCacheDatabase.rawQuery(sb.toString(), null);
+
+        if (cursor.moveToFirst()) {
+            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);
+        }
+        cursor.close();
+        return ret;
+    }
+
+    /**
+     * Remove a cache item.
+     * 
+     * @param url The url
+     */
+    @SuppressWarnings("deprecation")
+    void removeCache(String url) {
+        if (url == null || mCacheDatabase == null) {
+            return;
+        }
+
+        StringBuilder sb = new StringBuilder(256);
+        sb.append("DELETE FROM cache WHERE url = ");
+        DatabaseUtils.appendEscapedSQLString(sb, url);
+        mCacheDatabase.execSQL(sb.toString());
+    }
+
+    /**
+     * 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 = ?");
+            for (int i = 1; i < batchSize; i++) {
+                pathStr.append(" OR filepath = ?");
+            }
+            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();
+                    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 host The 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 host, String username, String password) {
+        if (host == null || mDatabase == null) {
+            return;
+        }
+
+        synchronized (mPasswordLock) {
+            final ContentValues c = new ContentValues();
+            c.put(PASSWORD_HOST_COL, host);
+            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 host The 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 host) {
+        if (host == 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[] { host }, 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/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java
new file mode 100644
index 0000000..00a9a47
--- /dev/null
+++ b/core/java/android/webkit/gears/DesktopAndroid.java
@@ -0,0 +1,113 @@
+// 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 BROWSER = "com.android.browser";
+  private static final String BROWSER_ACTIVITY = BROWSER + ".BrowserActivity";
+  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();
+
+    ComponentName browser = new ComponentName(BROWSER, BROWSER_ACTIVITY);
+
+    Intent viewWebPage = new Intent(Intent.ACTION_VIEW);
+    viewWebPage.setComponent(browser);
+    viewWebPage.setData(Uri.parse(url));
+
+    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/GearsPluginSettings.java b/core/java/android/webkit/gears/GearsPluginSettings.java
new file mode 100644
index 0000000..d36d3fb
--- /dev/null
+++ b/core/java/android/webkit/gears/GearsPluginSettings.java
@@ -0,0 +1,95 @@
+// 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.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+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 GearsPluginSettings {
+
+  private static final String TAG = "Gears-J-GearsPluginSettings";
+  private Context context;
+
+  public GearsPluginSettings(Plugin plugin) {
+    plugin.setClickHandler(new ClickHandler());
+  }
+
+  /**
+   * We do not need to call the dialog synchronously here (doing so
+   * actually cause a lot of problems as the main message loop is also
+   * blocked), which is why we simply call it via a thread.
+   */
+  private class ClickHandler implements PreferencesClickHandler {
+    public void handleClickEvent(Context aContext) {
+      context = aContext;
+      Thread startService = new Thread(new StartService());
+      startService.run();
+    }
+  }
+
+  private static native void runSettingsDialog(Context c);
+
+  /**
+   * StartService is the runnable we use to open the dialog.
+   * We bind the service to serviceConnection; upon
+   * onServiceConnected the dialog will be called from the
+   * native side using the runSettingsDialog method.
+   */
+  private class StartService implements Runnable {
+    public void run() {
+      HtmlDialogAndroid.bindToService(context, serviceConnection);
+    }
+  }
+
+  /**
+   * ServiceConnection instance.
+   * onServiceConnected is called upon connection with the service;
+   * we can then safely open the dialog.
+   */
+  private ServiceConnection serviceConnection = new ServiceConnection() {
+    public void onServiceConnected(ComponentName className, IBinder service) {
+      IGearsDialogService gearsDialogService =
+          IGearsDialogService.Stub.asInterface(service);
+      HtmlDialogAndroid.setGearsDialogService(gearsDialogService);
+      runSettingsDialog(context);
+      context.unbindService(serviceConnection);
+      HtmlDialogAndroid.setGearsDialogService(null);
+    }
+    public void onServiceDisconnected(ComponentName className) {
+      HtmlDialogAndroid.setGearsDialogService(null);
+    }
+  };
+}
diff --git a/core/java/android/webkit/gears/HtmlDialogAndroid.java b/core/java/android/webkit/gears/HtmlDialogAndroid.java
new file mode 100644
index 0000000..6209ab9
--- /dev/null
+++ b/core/java/android/webkit/gears/HtmlDialogAndroid.java
@@ -0,0 +1,174 @@
+// 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.webkit.CacheManager;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Utility class to call a modal HTML dialog on Android
+ */
+public class HtmlDialogAndroid {
+
+  private static final String TAG = "Gears-J-HtmlDialog";
+  private static final String DIALOG_PACKAGE = "com.android.browser";
+  private static final String DIALOG_SERVICE = DIALOG_PACKAGE
+      + ".GearsDialogService";
+  private static final String DIALOG_INTERFACE = DIALOG_PACKAGE
+      + ".IGearsDialogService";
+
+  private static IGearsDialogService gearsDialogService;
+
+  public static void setGearsDialogService(IGearsDialogService service) {
+    gearsDialogService = service;
+  }
+
+  /**
+   * Bind to the GearsDialogService.
+   */
+  public static boolean bindToService(Context context,
+      ServiceConnection serviceConnection) {
+    Intent dialogIntent = new Intent();
+    dialogIntent.setClassName(DIALOG_PACKAGE, DIALOG_SERVICE);
+    dialogIntent.setAction(DIALOG_INTERFACE);
+    return context.bindService(dialogIntent, serviceConnection,
+        Context.BIND_AUTO_CREATE);
+  }
+
+  /**
+   * Bind to the GearsDialogService synchronously.
+   * The service is started using our own defaultServiceConnection
+   * handler, and we wait until the handler notifies us.
+   */
+  public void synchronousBindToService(Context context) {
+    try {
+      if (bindToService(context, defaultServiceConnection)) {
+        if (gearsDialogService == null) {
+          synchronized(defaultServiceConnection) {
+            defaultServiceConnection.wait(3000); // timeout after 3s
+          }
+        }
+      }
+    } catch (InterruptedException e) {
+      Log.e(TAG, "exception: " + e);
+    }
+  }
+
+  /**
+   * Read the HTML content from the disk
+   */
+  public String readHTML(String filePath) {
+    FileInputStream inputStream = null;
+    String content = "";
+    try {
+      inputStream = new FileInputStream(filePath);
+      StringBuffer out = new StringBuffer();
+      byte[] buffer = new byte[4096];
+      for (int n; (n = inputStream.read(buffer)) != -1;) {
+        out.append(new String(buffer, 0, n));
+      }
+      content = out.toString();
+    } catch (IOException e) {
+      Log.e(TAG, "exception: " + e);
+    } finally {
+      if (inputStream != null) {
+        try {
+         inputStream.close();
+        } catch (IOException e) {
+          Log.e(TAG, "exception: " + e);
+        }
+      }
+    }
+    return content;
+  }
+
+  /**
+   * Open an HTML dialog synchronously and waits for its completion.
+   * The dialog is accessed through the GearsDialogService provided by
+   * the Android Browser.
+   * We can be called either directly, and then gearsDialogService will
+   * not be set and we will bind to the service synchronously, and unbind
+   * after calling the service, or called indirectly via GearsPluginSettings.
+   * In the latter case, GearsPluginSettings does the binding/unbinding.
+   */
+  public String showDialog(Context context, String htmlFilePath,
+      String arguments) {
+
+    CacheManager.endCacheTransaction();
+
+    String ret = null;
+    boolean synchronousCall = false;
+    if (gearsDialogService == null) {
+      synchronousCall = true;
+      synchronousBindToService(context);
+    }
+
+    try {
+      if (gearsDialogService != null) {
+        String htmlContent = readHTML(htmlFilePath);
+        if (htmlContent.length() > 0) {
+          ret = gearsDialogService.showDialog(htmlContent, arguments,
+              !synchronousCall);
+        }
+      } else {
+        Log.e(TAG, "Could not connect to the GearsDialogService!");
+      }
+      if (synchronousCall) {
+        context.unbindService(defaultServiceConnection);
+        gearsDialogService = null;
+      }
+    } catch (RemoteException e) {
+      Log.e(TAG, "remote exception: " + e);
+      gearsDialogService = null;
+    }
+
+    CacheManager.startCacheTransaction();
+
+    return ret;
+  }
+
+  private ServiceConnection defaultServiceConnection =
+      new ServiceConnection() {
+    public void onServiceConnected(ComponentName className, IBinder service) {
+      synchronized (defaultServiceConnection) {
+        gearsDialogService = IGearsDialogService.Stub.asInterface(service);
+        defaultServiceConnection.notify();
+      }
+    }
+    public void onServiceDisconnected(ComponentName className) {
+      gearsDialogService = null;
+    }
+  };
+}
diff --git a/core/java/android/webkit/gears/HttpRequestAndroid.java b/core/java/android/webkit/gears/HttpRequestAndroid.java
new file mode 100644
index 0000000..8668c54
--- /dev/null
+++ b/core/java/android/webkit/gears/HttpRequestAndroid.java
@@ -0,0 +1,730 @@
+// 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.Log;
+import android.webkit.CacheManager;
+import android.webkit.CacheManager.CacheResult;
+import android.webkit.CookieManager;
+
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.util.CharArrayBuffer;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import javax.net.ssl.*;
+
+/**
+ * Performs the underlying HTTP/HTTPS GET and POST 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 the java.net.HttpURLConnection class 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 HttpRequestAndroid {
+  /** 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;
+
+  /** Enable/disable all logging in this class. */
+  private static boolean logEnabled = false;
+  /** The underlying HTTP or HTTPS network connection. */
+  private HttpURLConnection connection;
+  /** HTTP body stream, setup after connection. */
+  private InputStream inputStream;
+  /** The complete response line e.g "HTTP/1.0 200 OK" */
+  private String responseLine;
+  /** Request headers, as a lowercase key -> [ unmodified key, value ] map. */
+  private Map<String, String[]> requestHeaders =
+      new HashMap<String, String[]>();
+  /** Response headers, as a lowercase key -> [ unmodified key, value ] map. */
+  private Map<String, String[]> responseHeaders;
+  /** True if the child thread is in performing blocking IO. */
+  private boolean inBlockingOperation = false;
+  /** True when the thread acknowledges the abort. */
+  private boolean abortReceived = false;
+  /** The URL used for createCacheResult() */
+  private String cacheResultUrl;
+  /** CacheResult being saved into, if inserting a new cache entry. */
+  private CacheResult cacheResult;
+  /** Initialized by initChildThread(). Used to target abort(). */
+  private Thread childThread;
+
+  /**
+   * Convenience debug function. Calls Android logging mechanism.
+   * @param str String to log to the Android console.
+   */
+  private static void log(String str) {
+    if (logEnabled) {
+      Log.i(LOG_TAG, str);
+    }
+  }
+
+  /**
+   * Turn on/off logging in this class.
+   * @param on Logging enable state.
+   */
+  public static void enableLogging(boolean on) {
+    logEnabled = on;
+  }
+
+  /**
+   * Initialize childThread using the TLS value of
+   * Thread.currentThread(). Called on start up of the native child
+   * thread.
+   */
+  public synchronized void initChildThread() {
+    childThread = Thread.currentThread();
+  }
+
+  /**
+   * Analagous to the native-side HttpRequest::open() function. This
+   * initializes an underlying java.net.HttpURLConnection, 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 (logEnabled)
+      log("open " + method + " " + url);
+    // Reset the response between calls to open().
+    inputStream = null;
+    responseLine = null;
+    responseHeaders = null;
+    if (!method.equals("GET") && !method.equals("POST")) {
+      log("Method " + method + " not supported");
+      return false;
+    }
+    // Setup the connection. This doesn't go to the wire yet - it
+    // doesn't block.
+    try {
+      connection = (HttpURLConnection) new URL(url).openConnection();
+      connection.setRequestMethod(method);
+      // Manually follow redirects.
+      connection.setInstanceFollowRedirects(false);
+      // Manually cache.
+      connection.setUseCaches(false);
+      // Enable data output in POST method requests.
+      connection.setDoOutput(method.equals("POST"));
+      // Enable data input in non-HEAD method requests.
+      // TODO: HEAD requests not tested.
+      connection.setDoInput(!method.equals("HEAD"));
+      if (connection instanceof javax.net.ssl.HttpsURLConnection) {
+        // Verify the certificate matches the origin.
+        ((HttpsURLConnection) connection).setHostnameVerifier(
+            new StrictHostnameVerifier());
+      }
+      return true;
+    } catch (IOException e) {
+      log("Got IOException in open: " + e.toString());
+      return false;
+    }
+  }
+
+  /**
+   * 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 (childThread == null) {
+      log("interrupt() called but no child thread");
+      return;
+    }
+    if (inBlockingOperation) {
+      log("Interrupting blocking operation");
+      childThread.interrupt();
+    } else {
+      log("Nothing to interrupt");
+    }
+  }
+
+  /**
+   * 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 };
+    requestHeaders.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 = requestHeaders.get(name.toLowerCase());
+    if (value != null) {
+      return value[HEADERS_MAP_INDEX_VALUE];
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * 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 (responseHeaders != null) {
+      String[] value = responseHeaders.get(name.toLowerCase());
+      if (value != null) {
+        return value[HEADERS_MAP_INDEX_VALUE];
+      } else {
+        return null;
+      }
+    } else {
+      log("getResponseHeader() called but response not received");
+      return null;
+    } 
+  }
+
+  /**
+   * 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 (logEnabled)
+      log("Set response header " + name + ": " + value);
+    String mapValue[] = { name, value };
+    responseHeaders.put(name.toLowerCase(), mapValue);
+  }
+  
+  /**
+   * Apply the contents of the Map requestHeaders to the connection
+   * object. Calls to setRequestHeader() after this will not affect
+   * the connection.
+   */
+  private synchronized void applyRequestHeadersToConnection() {
+    Iterator<String[]> it = requestHeaders.values().iterator();
+    while (it.hasNext()) {
+      // Set the key case-sensitive.
+      String[] entry = it.next();
+      connection.setRequestProperty(
+          entry[HEADERS_MAP_INDEX_KEY],
+          entry[HEADERS_MAP_INDEX_VALUE]);
+    }
+  }
+
+  /**
+   * 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 (responseHeaders == null) {
+      log("getAllResponseHeaders() called but response not received");
+      return null;
+    }
+    String result = new String();
+    Iterator<String[]> it = responseHeaders.values().iterator();
+    while (it.hasNext()) {
+      String[] entry = it.next();
+      // Output the "key: value" lines.
+      result += entry[HEADERS_MAP_INDEX_KEY] + ": "
+          + entry[HEADERS_MAP_INDEX_VALUE] + HTTP_LINE_ENDING;
+    }
+    result += HTTP_LINE_ENDING;
+    return result;
+  }
+
+  /**
+   * 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 responseLine;
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * 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, requestHeaders);
+    if (serviceResponse == null) {
+      log("No response in LocalServer");
+      return false;
+    }
+    // LocalServer will handle this URL. Initialize stream and
+    // response.
+    inputStream = serviceResponse.getInputStream();
+    responseLine = serviceResponse.getStatusLine();
+    responseHeaders = serviceResponse.getResponseHeaders();
+    if (logEnabled)
+      log("Got response from LocalServer: " + responseLine);
+    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 =
+        requestHeaders.entrySet().iterator();
+    while (it.hasNext()) {
+      Map.Entry<String, String[]> entry = it.next();
+      cacheRequestHeaders.put(
+          entry.getKey(),
+          entry.getValue()[HEADERS_MAP_INDEX_VALUE]);
+    }
+    CacheResult cacheResult =
+        CacheManager.getCacheFile(url, cacheRequestHeaders);
+    if (cacheResult == null) {
+      if (logEnabled)
+        log("No CacheResult for " + url);
+      return false;
+    }
+    if (logEnabled)
+      log("Got CacheResult from browser cache");
+    // Check for expiry. -1 is "never", otherwise milliseconds since 1970.
+    // Can be compared to System.currentTimeMillis().
+    long expires = cacheResult.getExpires();
+    if (expires >= 0 && System.currentTimeMillis() >= expires) {
+      log("CacheResult expired "
+          + (System.currentTimeMillis() - expires)
+          + " milliseconds ago");
+      // Cache hit has expired. Do not return it.
+      return false;
+    }
+    // Setup the inputStream to come from the cache.
+    inputStream = cacheResult.getInputStream();
+    if (inputStream == null) {
+      // Cache result may have gone away.
+      log("No inputStream for CacheResult " + url);
+      return false;
+    }
+    // Cache hit. Parse headers.
+    synthesizeHeadersFromCacheResult(cacheResult);
+    return true;
+  }
+
+  /**
+   * Take the limited set of headers in a CacheResult and synthesize
+   * response headers.
+   * @param cacheResult A CacheResult to populate responseHeaders 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.
+    responseLine = "HTTP/1.1 " + statusCode + " " + statusMessage;
+    if (logEnabled)
+      log("Synthesized " + responseLine);
+    // Synthesize the returned headers from cache.
+    responseHeaders = 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) {
+      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 (logEnabled)
+      log("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 =
+        responseHeaders.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);
+    }
+    cacheResult = CacheManager.createCacheFile(
+        url, responseCode, cacheHeaders, mimeType, true);
+    if (cacheResult != null) {
+      if (logEnabled)
+        log("Saving into cache");
+      cacheResult.setEncoding(encoding);
+      cacheResultUrl = url;
+      return true;
+    } else {
+      log("Couldn't create cacheResult");
+      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 (cacheResult == null) {
+      log("appendCacheResult() called without a CacheResult initialized");
+      return false;
+    }
+    try {
+      cacheResult.getOutputStream().write(data, 0, bytes);
+    } catch (IOException ex) {
+      log("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 (cacheResult == null || cacheResultUrl == null) {
+      log("Tried to save cache result but createCacheResult not called");
+      return false;
+    }
+    if (logEnabled)
+      log("Saving cache result");
+    CacheManager.saveCacheFile(cacheResultUrl, cacheResult);
+    cacheResult = null;
+    cacheResultUrl = null;
+    return true;
+  }
+
+  /**
+   * Perform an HTTP request on the network. The underlying
+   * HttpURLConnection is connected to the remote server and the
+   * response headers are received.
+   * @return True if the connection succeeded and headers have been
+   *         received. False on connection failure.
+   */
+  public boolean connectToRemote() {
+    synchronized (this) {
+      // Transfer a snapshot of our internally maintained map of request
+      // headers to the connection object.
+      applyRequestHeadersToConnection();
+      // Note blocking I/O so abort() can interrupt us.
+      inBlockingOperation = true;
+    }
+    boolean success;
+    try {
+      if (logEnabled)
+        log("Connecting to remote");
+      connection.connect();
+      if (logEnabled)
+        log("Connected");
+      success = true;
+    } catch (IOException e) {
+      log("Got IOException in connect(): " + e.toString());
+      success = false;
+    } finally {
+      synchronized (this) {
+        // No longer blocking.
+        inBlockingOperation = false;
+      }
+    }
+    return success;
+  }
+
+  /**
+   * Receive all headers from the server and populate
+   * responseHeaders. This converts from the slightly odd format
+   * returned by java.net.HttpURLConnection to a simpler
+   * java.util.Map.
+   * @return True if headers are successfully received, False on
+   *         connection error.
+   */
+  public synchronized boolean parseHeaders() {
+    responseHeaders = new HashMap<String, String[]>();
+    /* HttpURLConnection contains a null terminated list of
+     * key->value response pairs. If the key is null, then the value
+     * contains the complete status line. If both key and value are
+     * null for an index, we've reached the end.
+     */
+    for (int i = 0; ; ++i) {
+      String key = connection.getHeaderFieldKey(i);
+      String value = connection.getHeaderField(i);
+      if (logEnabled)
+        log("header " + key + " -> " + value);
+      if (key == null && value == null) {
+        // End of list.
+        break;
+      } else if (key == null) {
+        // The pair with null key has the complete status line in
+        // the value, e.g "HTTP/1.0 200 OK".
+        responseLine = value;
+      } else if (value != null) {
+        // If key and value are non-null, this is a response pair, e.g
+        // "Content-Length" -> "5". Use setResponseHeader() to
+        // correctly deal with case-insensitivity of the key.
+        setResponseHeader(key, value);
+      } else {
+        // The key is non-null but value is null. Unexpected
+        // condition.
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * 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 int receive(byte[] buf) {
+    if (inputStream == 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 {
+        inputStream = connection.getInputStream();
+      } catch (IOException inputException) {
+        log("Failed to connect InputStream: " + inputException);
+        // Not unexpected. For example, 404 response return headers,
+        // and sometimes a body with a detailed error. Try the error
+        // stream.
+        inputStream = connection.getErrorStream();
+        if (inputStream == null) {
+          // No error stream either. Treat as a 0 byte response.
+          log("No InputStream");
+          return 0; // EOF.
+        }
+      }
+    }
+    synchronized (this) {
+      // Note blocking I/O so abort() can interrupt us.
+      inBlockingOperation = true;
+    }
+    int ret;
+    try {
+      int got = inputStream.read(buf);
+      if (got > 0) {
+        // Got some bytes, not EOF.
+        ret = got;
+      } else {
+        // EOF.
+        inputStream.close();
+        ret = 0;
+      }
+    } catch (IOException e) {
+      // An abort() interrupts us by calling close() on our stream.
+      log("Got IOException in inputStream.read(): " + e.toString());
+      ret = -1;
+    } finally {
+      synchronized (this) {
+        // No longer blocking.
+        inBlockingOperation = false;
+      }
+    }
+    return ret;
+  }
+
+  /**
+   * For POST method requests, send a stream of data provided by the
+   * native side in repeated callbacks.
+   * @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) {
+    synchronized (this) {
+      // Note blocking I/O so abort() can interrupt us.
+      inBlockingOperation = true;
+    }
+    boolean success;
+    try {
+      OutputStream outputStream = connection.getOutputStream();
+      if (data == null && bytes == 0) {
+        outputStream.close();
+      } else {
+        outputStream.write(data, 0, bytes);
+      }
+      success = true;
+    } catch (IOException e) {
+      log("Got IOException in post: " + e.toString());
+      success = false;
+    } finally {
+      synchronized (this) {
+        // No longer blocking.
+        inBlockingOperation = false;
+      }
+    }
+    return success;
+  }
+}
diff --git a/core/java/android/webkit/gears/IGearsDialogService.java b/core/java/android/webkit/gears/IGearsDialogService.java
new file mode 100644
index 0000000..82a3bd9
--- /dev/null
+++ b/core/java/android/webkit/gears/IGearsDialogService.java
@@ -0,0 +1,107 @@
+/*
+ * This file is auto-generated.  DO NOT MODIFY.
+ * Original file: android.webkit.gears/IGearsDialogService.aidl
+ */
+package android.webkit.gears;
+import java.lang.String;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Binder;
+import android.os.Parcel;
+public interface IGearsDialogService extends android.os.IInterface
+{
+/** Local-side IPC implementation stub class. */
+public static abstract class Stub extends android.os.Binder implements android.webkit.gears.IGearsDialogService
+{
+private static final java.lang.String DESCRIPTOR = "com.android.browser.IGearsDialogService";
+/** Construct the stub at attach it to the interface. */
+public Stub()
+{
+this.attachInterface(this, DESCRIPTOR);
+}
+/**
+ * Cast an IBinder object into an IGearsDialogService interface,
+ * generating a proxy if needed.
+ */
+public static android.webkit.gears.IGearsDialogService asInterface(android.os.IBinder obj)
+{
+if ((obj==null)) {
+return null;
+}
+android.webkit.gears.IGearsDialogService in = (android.webkit.gears.IGearsDialogService)obj.queryLocalInterface(DESCRIPTOR);
+if ((in!=null)) {
+return in;
+}
+return new android.webkit.gears.IGearsDialogService.Stub.Proxy(obj);
+}
+public android.os.IBinder asBinder()
+{
+return this;
+}
+public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+{
+switch (code)
+{
+case INTERFACE_TRANSACTION:
+{
+reply.writeString(DESCRIPTOR);
+return true;
+}
+case TRANSACTION_showDialog:
+{
+data.enforceInterface(DESCRIPTOR);
+java.lang.String _arg0;
+_arg0 = data.readString();
+java.lang.String _arg1;
+_arg1 = data.readString();
+boolean _arg2;
+_arg2 = (0!=data.readInt());
+java.lang.String _result = this.showDialog(_arg0, _arg1, _arg2);
+reply.writeNoException();
+reply.writeString(_result);
+return true;
+}
+}
+return super.onTransact(code, data, reply, flags);
+}
+private static class Proxy implements android.webkit.gears.IGearsDialogService
+{
+private android.os.IBinder mRemote;
+Proxy(android.os.IBinder remote)
+{
+mRemote = remote;
+}
+public android.os.IBinder asBinder()
+{
+return mRemote;
+}
+public java.lang.String getInterfaceDescriptor()
+{
+return DESCRIPTOR;
+}
+public java.lang.String showDialog(java.lang.String htmlContent, java.lang.String dialogArguments, boolean inSettings) throws android.os.RemoteException
+{
+android.os.Parcel _data = android.os.Parcel.obtain();
+android.os.Parcel _reply = android.os.Parcel.obtain();
+java.lang.String _result;
+try {
+_data.writeInterfaceToken(DESCRIPTOR);
+_data.writeString(htmlContent);
+_data.writeString(dialogArguments);
+_data.writeInt(((inSettings)?(1):(0)));
+mRemote.transact(Stub.TRANSACTION_showDialog, _data, _reply, 0);
+_reply.readException();
+_result = _reply.readString();
+}
+finally {
+_reply.recycle();
+_data.recycle();
+}
+return _result;
+}
+}
+static final int TRANSACTION_showDialog = (IBinder.FIRST_CALL_TRANSACTION + 0);
+}
+public java.lang.String showDialog(java.lang.String htmlContent, java.lang.String dialogArguments, boolean inSettings) throws android.os.RemoteException;
+}
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
new file mode 100644
index 0000000..95fc30f
--- /dev/null
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -0,0 +1,497 @@
+// 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 =
+      HttpRequestAndroid.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 =
+      HttpRequestAndroid.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(HttpRequestAndroid.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) {
+      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) {
+      if (logEnabled)
+        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 Android logging mechanism.
+   * @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..19b1ce0
--- /dev/null
+++ b/core/java/android/widget/AbsListView.java
@@ -0,0 +1,3196 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Configuration;
+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.TextWatcher;
+import android.util.AttributeSet;
+import android.view.Gravity;
+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.WindowManagerImpl;
+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
+ */
+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;
+
+    /**
+     * 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;
+
+    /**
+     * 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 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;
+
+    // TODO: REMOVE WHEN WE'RE DONE WITH PROFILING
+    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;
+
+    /**
+     * 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);
+
+        a.recycle();
+    }
+
+    /**
+     * 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 (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)
+     */
+    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
+     */
+    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);
+    }
+
+    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
+     */
+    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;
+        }
+
+        // Don't restore the type filter window when there is no keyboard
+        int keyboardHidden = getContext().getResources().getConfiguration().keyboardHidden;
+        if (keyboardHidden != Configuration.KEYBOARDHIDDEN_YES) {
+            String filterText = ss.filter;
+            setFilterText(filterText);
+        }
+        requestLayout();
+    }
+
+    /**
+     * Sets the initial value for the text filter.
+     * @param filterText The text to use for the filter.
+     * 
+     * @see #setTextFilterEnabled
+     */
+    public void setFilterText(String filterText) {
+        if (mTextFilterEnabled && filterText != null && filterText.length() > 0) {
+            createTextFilter(false);
+            // This is going to call our listener onTextChanged, but we are
+            // not ready to bring up a window yet
+            mTextFilter.setText(filterText);
+            mTextFilter.setSelection(filterText.length());
+            if (mAdapter instanceof Filterable) {
+                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();
+            }
+        }
+    }
+
+    @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) {
+            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;
+        }
+        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) {
+                return Math.max(mFirstPosition * 100 - (top * 100) / height, 0);
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        return Math.max(mItemCount * 100, 0);
+    }
+
+    @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;
+    }
+
+    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();
+        }
+    }
+
+    /**
+     * @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()) {
+            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);
+        }
+        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 int index = mSelectedPosition - mFirstPosition;
+                performItemClick(getChildAt(index), mSelectedPosition, mSelectedRowId);
+                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);
+        int touchSlop = ViewConfiguration.getTouchSlop();
+        if (distance > touchSlop) {
+            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 = this.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) {
+        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 ((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.getMinimumFlingVelocity()) &&
+                        (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 boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        int x = (int) ev.getX();
+        int y = (int) ev.getY();
+        View v;
+        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);
+        }
+    }
+
+    private 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;
+            if (mOnScrollListener != null) {
+                mOnScrollListener.onScrollStateChanged(AbsListView.this,
+                        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 >= 0) {
+            mLayoutMode = LAYOUT_SPECIFIC;
+            setSelectionInt(selectedPos);
+        }
+
+        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) {
+            int screenHeight = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight();
+            final int[] xy = mLocation;
+            getLocationOnScreen(xy);
+            int bottomGap = screenHeight - xy[1] - getHeight() + 20;
+            mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+                    xy[0], bottomGap);
+            // Make sure we get focus if we are showing the popup
+            checkFocus();
+        }
+    }
+
+    /**
+     * 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) {
+            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.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..1fa7318
--- /dev/null
+++ b/core/java/android/widget/AbsSeekBar.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.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.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;
+    
+    private static final int NO_ALPHA = 0xFF;
+    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();
+    }
+
+    @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));
+        }
+    }
+    
+    @Override
+    void onProgressRefresh(float scale, boolean fromTouch) { 
+        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:
+                onStartTrackingTouch();
+                trackTouchEvent(event);
+                break;
+                
+            case MotionEvent.ACTION_MOVE:
+                trackTouchEvent(event);
+                break;
+                
+            case MotionEvent.ACTION_UP:
+                trackTouchEvent(event);
+                onStopTrackingTouch();
+                break;
+                
+            case MotionEvent.ACTION_CANCEL:
+                onStopTrackingTouch();
+                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);
+    }
+    
+    /**
+     * 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() {
+    }
+
+}
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..36a3b10
--- /dev/null
+++ b/core/java/android/widget/AbsoluteLayout.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.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>
+ */
+@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..e952dd5
--- /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..e096612
--- /dev/null
+++ b/core/java/android/widget/AdapterView.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 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.
+     */
+    public int getSelectedItemPosition() {
+        return mNextSelectedPosition;
+    }
+
+    /**
+     * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
+     * if nothing is selected.
+     */
+    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.)
+     */
+    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/AnalogClock.java b/core/java/android/widget/AnalogClock.java
new file mode 100644
index 0000000..808104e
--- /dev/null
+++ b/core/java/android/widget/AnalogClock.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.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.util.AttributeSet;
+import android.view.View;
+import android.pim.Time;
+
+import java.util.TimeZone;
+
+/**
+ * This widget display an analogic clock with two hands for hours and
+ * minutes.
+ */
+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 long mLastTime;
+
+    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();
+
+        onTimeChanged();
+        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);
+        }
+    }
+
+    @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() {
+        long time = System.currentTimeMillis();
+        mCalendar.set(time);
+        mLastTime = time;
+
+        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());
+            } else {
+                mCalendar = new Time(); 
+            }
+
+            onTimeChanged();
+            
+            invalidate();
+        }
+    };
+}
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
new file mode 100755
index 0000000..582117f
--- /dev/null
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -0,0 +1,383 @@
+/*
+**
+** 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.ApplicationInfo;
+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.util.ArrayList;
+import java.util.HashMap;
+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 String TAG = "AppSecurityPermissions";
+    private boolean localLOGV = false;
+    private Context mContext;
+    private LayoutInflater mInflater;
+    private PackageManager mPm;
+    private LinearLayout mPermsView;
+    private HashMap<String, String> mDangerousMap;
+    private HashMap<String, String> mNormalMap;
+    private ArrayList<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, String> mGroupLabelCache;
+    private View mNoPermsView;
+
+    public AppSecurityPermissions(Context context) {
+        this(context, null);
+    }
+
+    public AppSecurityPermissions(Context context, ArrayList<PermissionInfo> permList) {
+        mContext = context;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPm = context.getPackageManager();
+        mPermsList = permList;
+        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);
+    }
+
+    public void setSecurityPermissionsView() {
+        setPermissions(mPermsList);
+    }
+
+    public void setSecurityPermissionsView(Uri pkgURI) {
+        final String archiveFilePath = pkgURI.getPath();
+        PackageParser packageParser = new PackageParser(archiveFilePath);
+        File sourceFile = new File(archiveFilePath);
+        DisplayMetrics metrics = new DisplayMetrics();
+        metrics.setToDefaults();
+        PackageParser.Package pkgInfo = packageParser.parsePackage(sourceFile,
+                archiveFilePath, metrics, 0);
+        mPermsList = generatePermissionsInfo(pkgInfo.requestedPermissions);
+        //For packages that havent been installed we need the application info object
+        //to load the labels and other resources.
+        setPermissions(mPermsList, pkgInfo.applicationInfo);
+    }
+
+    public void setSecurityPermissionsView(PackageInfo pInfo) {
+        mPermsList = generatePermissionsInfo(pInfo.requestedPermissions);
+        setPermissions(mPermsList);
+    }
+
+    public View getPermissionsView() {
+        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, String permDesc) {
+        if(groupDesc == null) {
+            return permDesc;
+        }
+        groupDesc = canonicalizeGroupDesc(groupDesc);
+        if(permDesc == null) {
+            return groupDesc;
+        }
+        return String.format(mPermFormat, groupDesc, permDesc);
+    }
+
+    /**
+     * Utility method that concatenates two strings defined by mPermFormat.
+     */
+    private String formatPermissions(String groupDesc, CharSequence permDesc) {
+        groupDesc = canonicalizeGroupDesc(groupDesc);
+        if(permDesc == null) {
+            return groupDesc;
+        }
+        // Format only if str1 and str2 are not null.
+        return formatPermissions(groupDesc, permDesc.toString());
+    }
+
+    private ArrayList<PermissionInfo> generatePermissionsInfo(String[] strList) {
+        ArrayList<PermissionInfo> permInfoList = new ArrayList<PermissionInfo>();
+        if(strList == null) {
+            return permInfoList;
+        }
+        PermissionInfo tmpPermInfo = null;
+        for(int i = 0; i < strList.length; i++) {
+            try {
+                tmpPermInfo = mPm.getPermissionInfo(strList[i], 0);
+                permInfoList.add(tmpPermInfo);
+            } catch (NameNotFoundException e) {
+                Log.i(TAG, "Ignoring unknown permisison:"+strList[i]);
+                continue;
+            }
+        }
+        return permInfoList;
+    }
+
+    private ArrayList<PermissionInfo> generatePermissionsInfo(ArrayList<String> strList) {
+        ArrayList<PermissionInfo> permInfoList = new ArrayList<PermissionInfo>();
+        if(strList != null) {
+            PermissionInfo tmpPermInfo = null;
+            for(String permName:strList) {
+                try {
+                    tmpPermInfo = mPm.getPermissionInfo(permName, 0);
+                    permInfoList.add(tmpPermInfo);
+                } catch (NameNotFoundException e) {
+                    Log.i(TAG, "Ignoring unknown permisison:"+permName);
+                    continue;
+                }
+            }
+        }
+        return permInfoList;
+    }
+
+    private String getGroupLabel(String grpName) {
+        if (grpName == null) {
+            //return default label
+            return mDefaultGrpLabel;
+        }
+        String 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;
+        }
+        String 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) {
+        HashMap<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
+        LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
+        permListView.removeAllViews();
+
+        Set<String> permInfoStrSet = permInfoMap.keySet();
+        for (String loopPermGrpInfoStr : permInfoStrSet) {
+            String 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(String 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;
+    }
+
+    private void setPermissions(ArrayList<PermissionInfo> permList) {
+        setPermissions(permList, null);    
+    }
+    
+    private void setPermissions(ArrayList<PermissionInfo> permList, ApplicationInfo appInfo) {
+        mDangerousMap = new HashMap<String, String>();
+        mNormalMap = new HashMap<String, String>();
+        mGroupLabelCache = new HashMap<String, String>();
+        //add the default label so that uncategorized permissions can go here
+        mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
+        if (permList != null) {
+            for (PermissionInfo pInfo : permList) {
+                if(!isDisplayablePermission(pInfo)) {
+                    continue;
+                }
+                String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
+                HashMap<String, String> permInfoMap =
+                    (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ?
+                            mDangerousMap : mNormalMap;
+                // Check to make sure we have a label for the group
+                if (getGroupLabel(grpName) == null) {
+                    continue;
+                }
+                CharSequence permDesc = pInfo.loadLabel(mPm);
+                String grpDesc = permInfoMap.get(grpName);
+                permInfoMap.put(grpName, formatPermissions(grpDesc, permDesc));
+                if(localLOGV) Log.i(TAG, pInfo.name + "    :  " + permDesc+"    :    " + grpName);
+            }
+        }
+
+        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..fe50a01
--- /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 referecnes
+ * 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 spcified 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 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 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..e1f6fa8
--- /dev/null
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+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<String> adapter = new ArrayAdapter<String>(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
+ */
+public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
+    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 Drawable mDropDownListHighlight;
+
+    private AdapterView.OnItemClickListener mItemClickListener;
+    private AdapterView.OnItemSelectedListener mItemSelectedListener;
+
+    private final DropDownItemClickListener mDropDownItemClickListener =
+            new DropDownItemClickListener();
+
+    private boolean mTextChanged;
+
+    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);
+
+        mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
+                R.layout.simple_dropdown_hint);
+
+        a.recycle();
+
+        setFocusable(true);
+    }
+
+    /**
+     * 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 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
+     */
+    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
+     */
+    public AdapterView.OnItemSelectedListener getItemSelectedListener() {
+        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>
+     *
+     * @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 onKeyUp(int keyCode, KeyEvent event) {
+        if (isPopupShowing()) {
+            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()) {
+            // 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;
+
+            // 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
+            } else if (keyCode != KeyEvent.KEYCODE_SPACE) {
+                boolean consumed = mDropDownList.onKeyDown(keyCode, event);
+
+                if (consumed) {
+                    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{
+                    int index = mDropDownList.getSelectedItemPosition();
+                    switch (keyCode) {
+                        case KeyEvent.KEYCODE_DPAD_UP:
+                            if (index == 0) {
+                                return true;
+                            }
+                            break;
+                        // when the selection is at the bottom, we block the
+                        // event to avoid going to the next focusable widget
+                        case KeyEvent.KEYCODE_DPAD_DOWN:
+                            Adapter adapter = mDropDownList.getAdapter();
+                            if (index == adapter.getCount() - 1) {
+                                return true;
+                            }
+                            break;
+                    }
+                }
+            }
+        } else {
+            switch(keyCode) {
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                performValidation();
+            }
+        }
+
+        // when text is changed, inserted or deleted, we attempt to show
+        // the drop down
+        boolean openBefore = isPopupShowing();
+        mTextChanged = false;
+
+        boolean handled = super.onKeyDown(keyCode, event);
+
+        // 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 (openBefore && !isPopupShowing()) {
+            return handled;
+        }
+
+        if (mTextChanged) { // would have been set in onTextChanged()
+            // 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(), keyCode);
+                }
+            } else {
+                // drop down is automatically dismissed when enough characters
+                // are deleted from the text view
+                dismissDropDown();
+                if (mFilter != null) {
+                    mFilter.filter(null);
+                }
+            }
+            return true;
+        }
+
+        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() {
+        return getText().length() >= mThreshold;
+    }
+
+    @Override
+    protected void onTextChanged(CharSequence text, int start, int before,
+                                 int after) {
+        super.onTextChanged(text, start, before, after);
+        mTextChanged = true;
+    }
+
+    /**
+     * <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>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
+     */
+    @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);
+    }
+
+    private void performCompletion(View selectedView, int position, long id) {
+        if (isPopupShowing()) {
+            Object selectedItem;
+            if (position == -1) {
+                selectedItem = mDropDownList.getSelectedItem();
+            } else {
+                selectedItem = mAdapter.getItem(position);
+            }
+            replaceText(convertSelectionToString(selectedItem));
+
+            if (mItemClickListener != null) {
+                final DropDownListView list = mDropDownList;
+
+                if (selectedView == null || position == -1) {
+                    selectedView = list.getSelectedView();
+                    position = list.getSelectedItemPosition();
+                    id = list.getSelectedItemId();
+                }
+                mItemClickListener.onItemClick(list, selectedView, position, id);
+            }
+        }
+
+        dismissDropDown();
+    }
+
+    /**
+     * <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) {
+        /*
+         * 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();
+        }
+    }
+
+    /**
+     * <p>Closes the drop down if present on screen.</p>
+     */
+    public void dismissDropDown() {
+        mPopup.dismiss();
+        if (mDropDownList != null) {
+            // start next time with no selection
+            mDropDownList.hideSelector();
+        }
+    }
+
+    @Override
+    protected boolean setFrame(int l, int t, int r, int b) {
+        boolean result = super.setFrame(l, t, r, b);
+
+        mPopup.update(this, getMeasuredWidth(), -1);
+
+        return result;
+    }
+
+    /**
+     * <p>Displays the drop down on screen.</p>
+     */
+    public void showDropDown() {
+        int height = buildDropDown();
+        if (mPopup.isShowing()) {
+            mPopup.update(this, getMeasuredWidth() - mPaddingLeft - mPaddingRight, height);
+        } else {
+            mPopup.setHeight(height);
+            mPopup.setWidth(getMeasuredWidth() - mPaddingLeft - mPaddingRight);
+            mPopup.showAsDropDown(this);
+        }
+    }
+
+    /**
+     * <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 (mDropDownList == null) {
+            Context context = getContext();
+
+            mDropDownList = new DropDownListView(context);
+            mDropDownList.setSelector(mDropDownListHighlight);
+            mDropDownList.setAdapter(mAdapter);
+            mDropDownList.setVerticalFadingEdgeEnabled(true);
+            mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
+
+            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(getMeasuredWidth(), 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);
+        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;
+        }
+    }
+
+    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;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    static public interface Validator {
+        /**
+         * @return true if the text currently in the text editor is valid.
+         */
+        boolean isValid(CharSequence text);
+
+        /**
+         * @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.
+         */
+        CharSequence fixText(CharSequence invalidText);
+    }
+
+    private Validator mValidator = null;
+    
+    public void setValidator(Validator validator) {
+        mValidator = validator;
+    }
+
+    /**
+     * Returns the Validator set with {@link #setValidator},
+     * or <code>null</code> if it was not set.
+     */
+    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. 
+     */
+    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;
+    }
+}
diff --git a/core/java/android/widget/BaseAdapter.java b/core/java/android/widget/BaseAdapter.java
new file mode 100644
index 0000000..1921d73
--- /dev/null
+++ b/core/java/android/widget/BaseAdapter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+    }
+    
+    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..3a8bb2a
--- /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 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 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 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 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..f2868af
--- /dev/null
+++ b/core/java/android/widget/Button.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+
+/**
+ * <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>
+ */
+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..31d2063
--- /dev/null
+++ b/core/java/android/widget/Chronometer.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.pim.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";
+
+    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;
+
+    /**
+     * 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.
+     */
+    public void setBase(long base) {
+        mBase = base;
+        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.
+     */
+    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;
+    }
+
+    /**
+     * 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();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mVisible = false;
+        updateRunning();
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mVisible = visibility == VISIBLE;
+        updateRunning();
+    }
+
+    private void updateText(long now) {
+        long seconds = now - mBase;
+        seconds /= 1000;
+        String text = DateUtils.formatElapsedTime(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());
+                mHandler.sendMessageDelayed(Message.obtain(), 1000);
+            }
+            mRunning = running;
+        }
+    }
+    
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message m) {
+            if (mStarted) {
+                updateText(SystemClock.elapsedRealtime());
+                sendMessageDelayed(Message.obtain(), 1000);
+            }
+        }
+    };
+}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
new file mode 100644
index 0000000..e56a741
--- /dev/null
+++ b/core/java/android/widget/CompoundButton.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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();
+        }
+    }
+    
+    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..cacaeab
--- /dev/null
+++ b/core/java/android/widget/CursorAdapter.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 (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;
+    }
+
+    private class ChangeObserver extends ContentObserver {
+        public ChangeObserver() {
+            super(new Handler());
+        }
+
+        @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/CursorFilter.java b/core/java/android/widget/CursorFilter.java
new file mode 100644
index 0000000..afd5b10
--- /dev/null
+++ b/core/java/android/widget/CursorFilter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 != 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..fa8fd4b
--- /dev/null
+++ b/core/java/android/widget/CursorTreeAdapter.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 (mCursor != null) {
+                mCursor.unregisterContentObserver(mContentObserver);
+                mCursor.unregisterDataSetObserver(mDataSetObserver);
+            }
+            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..c03bd32
--- /dev/null
+++ b/core/java/android/widget/DatePicker.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.pim.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;    
+    
+    private final int mStartYear;
+    private final int mEndYear;
+    
+    /**
+     * 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, // we are the parent
+            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);
+
+        mStartYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR);
+        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..3ca2c81
--- /dev/null
+++ b/core/java/android/widget/DigitalClock.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 android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.pim.DateFormat;
+import android.provider.Settings;
+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() {
+        String value = Settings.System.getString(
+                getContext().getContentResolver(),
+                Settings.System.TIME_12_24);
+
+        if (value == null || value.equals("12"))
+            return false;
+        return true;
+    }
+
+    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..e89a2bd
--- /dev/null
+++ b/core/java/android/widget/EditText.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.widget;
+
+import android.text.*;
+import android.text.method.*;
+import android.content.Context;
+import android.content.res.Resources;
+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);
+    }
+}
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..ddedea3
--- /dev/null
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/*
+ * 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 new PositionMetadata(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 new PositionMetadata(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 new PositionMetadata(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 from 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 new PositionMetadata(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.
+     */
+    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 new PositionMetadata(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 new PositionMetadata(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 new PositionMetadata(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 new PositionMetadata(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 new PositionMetadata(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;
+
+        if (pos.type == ExpandableListPosition.CHILD) {
+            return mExpandableListAdapter.isChildSelectable(pos.groupPos, pos.childPos);
+        } else {
+            // Groups are always selectable
+            return true;
+        }
+    }
+
+    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);
+
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            return mExpandableListAdapter
+                    .getGroup(posMetadata.position.groupPos);
+        } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+            return mExpandableListAdapter.getChild(posMetadata.position.groupPos,
+                    posMetadata.position.childPos);
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Flat list position is of unknown type");
+        }
+    }
+
+    public long getItemId(int flatListPos) {
+        final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+        final long groupId = mExpandableListAdapter.getGroupId(posMetadata.position.groupPos);
+        
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            return mExpandableListAdapter.getCombinedGroupId(groupId);
+        } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+            final long childId = mExpandableListAdapter.getChildId(posMetadata.position.groupPos,
+                    posMetadata.position.childPos);
+            return mExpandableListAdapter.getCombinedChildId(groupId, childId);
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Flat list position is of unknown type");
+        }
+    }
+
+    public View getView(int flatListPos, View convertView, ViewGroup parent) {
+        final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+
+        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
+            return mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata
+                    .isExpanded(), convertView, parent);
+        } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
+            final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos;
+            
+            final View view = mExpandableListAdapter.getChildView(posMetadata.position.groupPos,
+                    posMetadata.position.childPos, isLastChild, convertView, parent);
+            
+            return view;
+        } else {
+            // TODO: clean exit
+            throw new RuntimeException("Flat list position is of unknown type");
+        }
+    }
+
+    @Override
+    public int getItemViewType(int flatListPos) {
+        final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
+
+        if (pos.type == ExpandableListPosition.GROUP) {
+            return 0;
+        } else {
+            return 1;
+        }
+    }
+
+    @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.
+     */
+    private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh) {
+        final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
+        final int egmlSize = egml.size();
+        int curFlPos = 0;
+        
+        /* Update child count as we go through */
+        mTotalExpChildrenCount = 0;
+        
+        GroupMetadata curGm;
+        int gChildrenCount;
+        int lastGPos = 0;
+        for (int i = 0; i < egmlSize; i++) {
+            /* Store in local variable since we'll access freq */
+            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) {
+        return collapseGroup(getFlattenedPos(new ExpandableListPosition(ExpandableListPosition.GROUP,
+                groupPos, -1, -1)));
+    }
+    
+    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);
+        
+        // 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) {
+        return expandGroup(getFlattenedPos(new ExpandableListPosition(ExpandableListPosition.GROUP, 
+                groupPos, -1, -1)));
+    }
+
+    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 = new GroupMetadata();
+        
+        expandedGm.gPos = posMetadata.position.groupPos;
+        expandedGm.flPos = GroupMetadata.REFRESH;
+        expandedGm.lastChildFlPos = GroupMetadata.REFRESH;
+        
+        mExpGroupMetadataList.add(posMetadata.groupInsertIndex, expandedGm);
+
+        // Refresh the metadata
+        refreshExpGroupMetadataList(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);
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        ExpandableListAdapter adapter = getAdapter();
+        return adapter != null ? adapter.isEmpty() : true;
+    }
+
+    protected class MyDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            refreshExpGroupMetadataList(true);
+            
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onInvalidated() {
+            refreshExpGroupMetadataList(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 {
+        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;
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(flPos);
+            dest.writeInt(lastChildFlPos);
+            dest.writeInt(gPos);
+        }
+        
+        public static final Parcelable.Creator<GroupMetadata> CREATOR =
+                new Parcelable.Creator<GroupMetadata>() {
+            
+            public GroupMetadata createFromParcel(Parcel in) {
+                GroupMetadata gm = new GroupMetadata();
+                gm.flPos = in.readInt();
+                gm.lastChildFlPos = in.readInt();
+                gm.gPos = in.readInt();
+                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 {
+        /** 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;
+        
+        public PositionMetadata(int flatListPos, int type, int groupPos,
+                int childPos) {
+            position = new ExpandableListPosition(type, groupPos, childPos, flatListPos);
+        }
+        
+        protected PositionMetadata(int flatListPos, int type, int groupPos,
+                int childPos, GroupMetadata groupMetadata, int groupInsertIndex) {
+            position = new ExpandableListPosition(type, groupPos, childPos, flatListPos);
+            
+            this.groupMetadata = groupMetadata;
+            this.groupInsertIndex = groupInsertIndex;
+        }
+        
+        /**
+         * 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..71e970c
--- /dev/null
+++ b/core/java/android/widget/ExpandableListPosition.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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 {
+    /**
+     * 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;
+    
+    ExpandableListPosition(int type, int groupPos, int childPos, int flatListPos) {
+        this.type = type;
+        this.flatListPos = flatListPos;
+        this.groupPos = groupPos;
+        this.childPos = childPos;
+    }
+
+    /**
+     * Used internally by the {@link #obtainChildPosition} and
+     * {@link #obtainGroupPosition} methods to construct a new object.
+     */
+    private ExpandableListPosition(int type, int groupPos, int childPos) {
+        this.type = type;
+        this.groupPos = groupPos;
+        this.childPos = childPos;
+    }
+    
+    long getPackedPosition() {
+        if (type == CHILD) return ExpandableListView.getPackedPositionForChild(groupPos, childPos);
+        else return ExpandableListView.getPackedPositionForGroup(groupPos);
+    }
+
+    static ExpandableListPosition obtainGroupPosition(int groupPosition) {
+        return new ExpandableListPosition(GROUP, groupPosition, 0);
+    }
+    
+    static ExpandableListPosition obtainChildPosition(int groupPosition, int childPosition) {
+        return new ExpandableListPosition(CHILD, groupPosition, childPosition);
+    }
+
+    static ExpandableListPosition obtainPosition(long packedPosition) {
+        if (packedPosition == ExpandableListView.PACKED_POSITION_VALUE_NULL) {
+            return null;
+        }
+        
+        final int type = ExpandableListView.getPackedPositionType(packedPosition) ==
+            ExpandableListView.PACKED_POSITION_TYPE_CHILD ? CHILD : GROUP;
+        
+        return new ExpandableListPosition(type, ExpandableListView
+                .getPackedPositionGroup(packedPosition), ExpandableListView
+                .getPackedPositionChild(packedPosition));
+    }
+    
+}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
new file mode 100644
index 0000000..138cace
--- /dev/null
+++ b/core/java/android/widget/ExpandableListView.java
@@ -0,0 +1,1057 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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;
+    
+    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);
+        
+        // Bounds of the indicator to be drawn
+        Rect indicatorRect = new Rect();
+        
+        // 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) {
+                // The left and right bounds are the same, so nothing will be drawn
+                continue;
+            }
+
+            // 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) continue;
+            
+            // Draw the indicator
+            indicator.setBounds(indicatorRect);
+            indicator.draw(canvas);
+        }
+
+        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;
+    }
+
+    @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
+                mChildDivider.setBounds(bounds);
+                mChildDivider.draw(canvas);
+                return;
+            }
+        }
+        
+        // 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);
+        
+        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)) {
+                        return true;
+                    }
+                }
+
+                /* Expand it */
+                mConnector.expandGroup(posMetadata);
+
+                playSoundEffect(SoundEffectConstants.CLICK);
+                
+                if (mOnGroupExpandListener != null) {
+                    mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
+                }
+            }
+            
+            return 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);
+            }
+            
+            return false;
+        }
+    }
+
+    /**
+     * 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) {
+        return mConnector.getUnflattenedPos(flatListPosition).position.getPackedPosition();
+    }
+    
+    /**
+     * 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) {
+        return mConnector.getFlattenedPos(ExpandableListPosition.obtainPosition(packedPosition)).
+            position.flatListPos;
+    }
+
+    /**
+     * 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); 
+        super.setSelection(mConnector.getFlattenedPos(elGroupPos).position.flatListPos);
+    }
+    
+    /**
+     * 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);
+        
+        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) {
+        ExpandableListPosition pos = mConnector.getUnflattenedPos(flatListPosition).position;
+        
+        id = getChildOrGroupId(pos);
+        
+        return new ExpandableListContextMenuInfo(view, pos.getPackedPosition(), 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/Filter.java b/core/java/android/widget/Filter.java
new file mode 100644
index 0000000..49888f7
--- /dev/null
+++ b/core/java/android/widget/Filter.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * <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 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();
+            args.constraint = constraint;
+            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);
+                    } 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..b4ed3ba
--- /dev/null
+++ b/core/java/android/widget/FrameLayout.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.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
+     */
+    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
+     */
+    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..acf9400
--- /dev/null
+++ b/core/java/android/widget/Gallery.java
@@ -0,0 +1,1338 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.
+ * 
+ * @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;
+    
+    /**
+     * 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();
+    
+    /**
+     * 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;
+
+    /**
+     * 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;
+    
+    private AdapterContextMenuInfo mContextMenuInfo;
+    
+    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(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) {
+            // This will get reset once we scroll into slots
+            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
+        // into on the screen is up-to-date with the user's selection
+        if (mSuppressSelectionChanged) {
+            mSuppressSelectionChanged = false;
+        }
+        
+        // Track the motion
+        trackMotionScroll(-1 * (int) distanceX);
+        
+        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);
+        }
+        
+        // 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;
+        }
+        
+        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);
+        }
+
+        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;
+        }
+        
+        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 (mItemCount > 0) {
+
+                dispatchPress(mSelectedChild);
+                postDelayed(new Runnable() {
+                    public void run() {
+                        dispatchUnpress();
+                    }
+                }, ViewConfiguration.getPressedStateDuration());
+
+                int selectedIndex = mSelectedPosition - mFirstPosition;
+                performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
+                        .getItemId(mSelectedPosition));
+            }
+            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..268bf84
--- /dev/null
+++ b/core/java/android/widget/GridView.java
@@ -0,0 +1,1828 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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) {
+        int last;
+        int nextLeft = mListPadding.left;
+
+        final int columnWidth = mColumnWidth;
+        final int horizontalSpacing = mHorizontalSpacing;
+
+        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;
+            }
+
+            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);
+            final int childViewType = mAdapter.getItemViewType(0);
+
+            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
+            if (lp == null) {
+                lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+                child.setLayoutParams(lp);
+            }
+            lp.viewType = childViewType;
+
+            final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+                    mListPadding.left + mListPadding.right, lp.width);
+
+            int lpHeight = lp.height;
+
+            int childHeightSpec;
+            if (lpHeight > 0) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            }
+
+            child.measure(childWidthSpec, childHeightSpec);
+            childHeight = child.getMeasuredHeight();
+
+            if (mRecycler.shouldRecycleViewType(childViewType)) {
+                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);
+            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);
+            moved = true;
+        } else if (direction == FOCUS_DOWN) {
+            mLayoutMode = LAYOUT_SET_SELECTION;
+            setSelectionInt(mItemCount - 1);
+            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));
+        }
+
+        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}, 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/ImageButton.java b/core/java/android/widget/ImageButton.java
new file mode 100644
index 0000000..5c56428
--- /dev/null
+++ b/core/java/android/widget/ImageButton.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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>
+ */
+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..b5d4e2d5
--- /dev/null
+++ b/core/java/android/widget/ImageView.java
@@ -0,0 +1,883 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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
+ */
+@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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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();
+    }
+
+    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 ? getHeight() : -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..de74fa4
--- /dev/null
+++ b/core/java/android/widget/LinearLayout.java
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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
+     */
+    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
+     */
+    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
+     */
+    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 = lp.height;
+                   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;
+                alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+
+                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 = lp.width;
+                    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;
+                alternativeMaxHeight = Math.max(alternativeMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+
+                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:
+                   childTop = mBottom - mTop - mPaddingBottom - mTotalLength;
+                   break;
+
+               case Gravity.CENTER_VERTICAL:
+                   childTop += ((mBottom - mTop - mPaddingTop - mPaddingBottom) -
+                           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:
+                    childLeft = mRight - mLeft - mPaddingRight - mTotalLength;
+                    break;
+
+                case Gravity.CENTER_HORIZONTAL:
+                    childLeft += ((mRight - mLeft - mPaddingLeft - mPaddingRight) -
+                            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
+     */
+    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();
+        }
+    }
+
+    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();
+        }
+    }
+
+    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..d52e51f
--- /dev/null
+++ b/core/java/android/widget/ListView.java
@@ -0,0 +1,3204 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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 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
+ */
+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;
+
+    // TODO: document
+    class FixedViewInfo {
+        public View view;
+        public Object data;
+        public boolean isSelectable;
+    }
+
+    private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
+    private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
+
+    Drawable mDivider;
+    int mDividerHeight;
+
+    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();
+
+    /**
+     * Used to save / restore the state of the focused child in {@link #layoutChildren()}
+     */
+    private SparseArray<Parcelable> mfocusRestoreChildState = new SparseArray<Parcelable>();
+
+
+    // 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);
+        } else {
+
+            // Else 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);
+            }
+        }
+
+        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);
+            final int childViewType = mAdapter.getItemViewType(0);
+
+            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
+            if (lp == null) {
+                lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+                child.setLayoutParams(lp);
+            }
+            lp.viewType = childViewType;
+
+            final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+                    mListPadding.left + mListPadding.right, lp.width);
+
+            int lpHeight = lp.height;
+
+            int childHeightSpec;
+            if (lpHeight > 0) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            }
+
+            child.measure(childWidthSpec, childHeightSpec);
+
+            childWidth = child.getMeasuredWidth();
+            childHeight = child.getMeasuredHeight();
+
+            if (mRecycler.shouldRecycleViewType(childViewType)) {
+                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(
+                    MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
+                    0, NO_POSITION, heightSize, -1);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    /**
+     * 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(final int widthMeasureSpec, final 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;
+        for (i = startPosition; i <= endPosition; ++i) {
+            child = obtainView(i);
+            final int childViewType = adapter.getItemViewType(i);
+
+            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
+            if (lp == null) {
+                lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+                child.setLayoutParams(lp);
+            }
+            lp.viewType = childViewType;
+
+            if (i > 0) {
+                // Count the divider for all but one child
+                returnedHeight += dividerHeight;
+            }
+
+            child.measure(widthMeasureSpec, lp.height >= 0
+                    ? MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)
+                            : MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+
+            // Recycle the view before we possibly return from the method
+            if (recycleBin.shouldRecycleViewType(childViewType)) {
+                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 its state
+                        focusLayoutRestoreDirectChild.saveHierarchyState(mfocusRestoreChildState);
+
+                        // remember the specific view that had focus
+                        focusLayoutRestoreView = findFocus();
+                    }
+                }
+                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();
+                   }
+
+                   if (sel == focusLayoutRestoreDirectChild) {
+                       focusLayoutRestoreDirectChild.restoreHierarchyState(mfocusRestoreChildState);
+                   }
+               } 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();
+                   focusLayoutRestoreDirectChild.restoreHierarchyState(mfocusRestoreChildState);
+               }
+            }
+
+            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
+     *
+     * @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) {
+        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);
+                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);
+                }
+                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);
+                }
+                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();
+        }
+
+        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();
+            int i;
+
+            if (mStackFromBottom) {
+                int top;
+                int listTop = mListPadding.top;
+
+                for (i = 0; i < count; ++i) {
+                    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);
+                    }
+                }
+            } else {
+                int bottom;
+                int listBottom = getHeight() - mListPadding.bottom;
+
+                for (i = 0; i < count; ++i) {
+                    View child = getChildAt(i);
+                    bottom = child.getBottom();
+                    if (bottom < listBottom) {
+                        bounds.top = bottom;
+                        bounds.bottom = bottom + dividerHeight;
+                        drawDivider(canvas, bounds, i);
+                    }
+                }
+            }
+        }
+
+        // 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
+        mDivider.setBounds(bounds);
+        mDivider.draw(canvas);
+    }
+
+    /**
+     * 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();
+        } else {
+            mDividerHeight = 0;
+        }
+        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();
+    }
+
+    @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..ad8433f
--- /dev/null
+++ b/core/java/android/widget/MediaController.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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_SPACE)) {
+            doPauseResume();
+            show(sDefaultTimeout);
+            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..59a9310
--- /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) {
+            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) {
+            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..6a7b1fb
--- /dev/null
+++ b/core/java/android/widget/PopupWindow.java
@@ -0,0 +1,803 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.WindowManagerImpl;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+/**
+ * <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 {
+    /**
+     * The height of the status bar so we know how much of the screen we can
+     * actually be displayed in.
+     * <p>
+     * TODO: This IS NOT the right way to do this.
+     * Instead of knowing how much of the screen is available, a popup that
+     * wants anchor and maximize space shouldn't be setting a height, instead
+     * the PopupViewContainer should have its layout height as fill_parent and
+     * properly position the popup.
+     */
+    private static final int STATUS_BAR_HEIGHT = 30;
+    
+    private boolean mIsShowing;
+
+    private View mContentView;
+    private View mPopupView;
+    private boolean mFocusable;
+
+    private int mWidth;
+    private int mHeight;
+
+    private int[] mDrawingLocation = new int[2];
+    private int[] mRootLocation = new int[2];
+    private Rect mTempRect = new Rect();
+    
+    private Context mContext;
+    private Drawable mBackground;
+
+    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
+    };
+    
+    /**
+     * <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;
+
+        TypedArray a =
+            context.obtainStyledAttributes(
+                attrs, com.android.internal.R.styleable.PopupWindow, defStyle, 0);
+
+        mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+
+        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) {
+        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 events
+     * This method has to be invoked before displaying the content view
+     * of the popup for the window flags to take effect and will be ignored
+     * if the pop up is already displayed. By default this flag is set to false
+     * which means the pop wont ignore cheek press dispatch events.
+     */
+    public void setIgnoreCheekPress() {
+        mIgnoreCheekPress = true;
+    }
+    
+
+    /**
+     * <p>Change the animation style for this popup.</p>
+     *
+     * @param animationStyle animation style to use when the popup appears and disappears
+     */
+    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.</p>
+     *
+     * @param contentView the new content for the popup
+     *
+     * @see #getContentView()
+     * @see #isShowing()
+     */
+    public void setContentView(View contentView) {
+        if (isShowing()) {
+            return;
+        }
+
+        mContentView = contentView;
+    }
+
+    /**
+     * <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}.</p>
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown.</p>
+     *
+     * @param focusable true if the popup should grab focus, false otherwise
+     *
+     * @see #isFocusable()
+     * @see #isShowing() 
+     */
+    public void setFocusable(boolean focusable) {
+        mFocusable = focusable;
+    }
+
+    /**
+     * <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;
+        }
+
+        mIsShowing = true;
+
+        WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken());
+        if (mAnimationStyle != -1) {
+            p.windowAnimations = mAnimationStyle;
+        }
+       
+        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>
+     *
+     * @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;
+        }
+
+        mIsShowing = true;
+
+        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
+        preparePopup(p);
+        if (mBackground != null) {
+            mPopupView.refreshDrawableState();
+        }
+        mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
+        if (mAnimationStyle == -1) {
+            p.windowAnimations = mAboveAnchor
+                    ? com.android.internal.R.style.Animation_DropDownUp
+                    : com.android.internal.R.style.Animation_DropDownDown;
+        } else {
+            p.windowAnimations = mAnimationStyle;
+        }
+        invokePopup(p);
+    }
+
+    /**
+     * <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) {
+            // 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,
+                    ViewGroup.LayoutParams.FILL_PARENT
+            );
+            popupViewContainer.setBackgroundDrawable(mBackground);
+            popupViewContainer.addView(mContentView, listParams);
+
+            if (p.height >= 0) {
+                // accomodate the popup's height to take into account the
+                // background's padding
+                p.height += popupViewContainer.getPaddingTop() +
+                        popupViewContainer.getPaddingBottom();
+            }
+            if (p.width >= 0) {
+                // accomodate the popup's width to take into account the
+                // background's padding
+                p.width += popupViewContainer.getPaddingLeft() +
+                        popupViewContainer.getPaddingRight();
+            }
+            mPopupView = popupViewContainer;
+        } else {
+            mPopupView = mContentView;
+        }
+        
+    }
+
+    /**
+     * <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) {
+        WindowManagerImpl wm = WindowManagerImpl.getDefault();
+        wm.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 = mWidth;
+        p.height = mHeight;
+        if (mBackground != null) {
+            p.format = mBackground.getOpacity();
+        } else {
+            p.format = PixelFormat.TRANSLUCENT;
+        }
+        if(mIgnoreCheekPress) {
+            p.flags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+        }
+        if (!mFocusable) {
+            p.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        }
+        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        p.token = token;
+        
+        return p;
+    }
+
+    /**
+     * <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;
+
+        if (p.y + p.height > WindowManagerImpl.getDefault().getDefaultDisplay().getHeight()) {
+            // 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
+            View root = anchor.getRootView();
+            root.getLocationInWindow(mRootLocation);
+            int delta = p.y + p.height - mRootLocation[1] - root.getHeight();
+
+            if (delta > 0 || p.x + p.width - mRootLocation[0] - root.getWidth() > 0) {
+                Rect r = new Rect(anchor.getScrollX(), anchor.getScrollY(),
+                        p.width, p.height + anchor.getMeasuredHeight());
+
+                onTop = !anchor.requestRectangleOnScreen(r, true);
+
+                if (onTop) {
+                    p.y -= anchor.getMeasuredHeight() + p.height;
+                } else {
+                    anchor.getLocationOnScreen(mDrawingLocation);
+                    p.x = mDrawingLocation[0] + xoff;
+                    p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+                }
+            }
+        }
+
+        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) {
+        // TODO: read comment on STATUS_BAR_HEIGHT
+        final int screenHeight = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight()
+                - STATUS_BAR_HEIGHT;
+
+        final int[] anchorPos = mDrawingLocation;
+        anchor.getLocationOnScreen(anchorPos);
+        anchorPos[1] -= STATUS_BAR_HEIGHT;
+
+        final int distanceFromAnchorToBottom = screenHeight - (anchorPos[1] + anchor.getHeight());
+        
+        // anchorPos[1] is distance from anchor to top of screen
+        int returnedHeight = Math.max(anchorPos[1], distanceFromAnchorToBottom);
+        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) {
+            WindowManagerImpl wm = WindowManagerImpl.getDefault();
+            wm.removeView(mPopupView);
+            if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
+                ((ViewGroup) mPopupView).removeView(mContentView);
+            }
+            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;
+    }
+    
+    /**
+     * <p>Updates the position and the dimension of the popup window. Width and
+     * height can be set to -1 to update location only.</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) {
+        if (width != -1) {
+            setWidth(width);
+        }
+
+        if (height != -1) {
+            setHeight(height);
+        }
+
+        if (!isShowing() || mContentView == null) {
+            return;
+        }
+
+        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+                mPopupView.getLayoutParams();
+
+        boolean update = false;
+
+        if (width != -1 && p.width != width) {
+            p.width = width;
+            update = true;
+        }
+
+        if (height != -1 && p.height != height) {
+            p.height = height;
+            update = true;
+        }
+
+        if (p.x != x) {
+            p.x = x;
+            update = true;
+        }
+
+        if (p.y != y) {
+            p.y = y;
+            update = true;
+        }
+
+        if (update) {
+            if (mPopupView != mContentView) {
+                final View popupViewContainer = mPopupView;
+                if (p.height >= 0) {
+                    // accomodate the popup's height to take into account the
+                    // background's padding
+                    p.height += popupViewContainer.getPaddingTop() +
+                            popupViewContainer.getPaddingBottom();
+                }
+                if (p.width >= 0) {
+                    // accomodate the popup's width to take into account the
+                    // background's padding
+                    p.width += popupViewContainer.getPaddingLeft() +
+                            popupViewContainer.getPaddingRight();
+                }
+            }
+
+            WindowManagerImpl wm = WindowManagerImpl.getDefault();
+            wm.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.</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.</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;
+        }
+
+        WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+                mPopupView.getLayoutParams();
+
+        int x = p.x;
+        int y = p.y;
+        findDropDownPosition(anchor, p, xoff, yoff);
+
+        update(x, y, width, height);
+    }
+    
+    /**
+     * Listener that is called when this popup window is dismissed.
+     */
+    interface OnDismissListener {
+        /**
+         * Called when this popup window is dismissed.
+         */
+        public void onDismiss();
+    }
+    
+    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 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 {
+                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..c1de010
--- /dev/null
+++ b/core/java/android/widget/ProgressBar.java
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.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.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);
+            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();
+    }
+
+    /*
+     * TODO: This is almost ready to be removed. This was used to support our
+     * old style of progress bars with the ticks. Need to check with designers
+     * on whether they can give us a transparent 'tick' overlay tile for our new
+     * gradient-based progress bars. (We still need the ticked progress bar for
+     * media player apps.) I'll remove this and add XML support if they want to
+     * do the overlay approach. If they want to just have a separate style for
+     * this legacy stuff, then we can keep it around.
+     */
+    
+    // TODO Remove all this once ShapeDrawable + shaders are supported through XML
+    private Drawable tileify(Drawable drawable) {
+        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] = createDrawableForTile(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));
+            }
+            
+            drawable = newBg;
+        }
+        return drawable;
+    }
+
+    // TODO Remove all this once ShapeDrawable + shaders are supported through XML
+    private Drawable createDrawableForTile(Drawable tileDrawable, boolean clip) {
+        if (!(tileDrawable instanceof BitmapDrawable)) return tileDrawable;
+
+        final Bitmap tileBitmap = ((BitmapDrawable) tileDrawable).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;
+    }
+    
+    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 = createDrawableForTile(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
+     */
+    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 mFromTouch;
+        
+        RefreshProgressRunnable(int id, int progress, boolean fromTouch) {
+            mId = id;
+            mProgress = progress;
+            mFromTouch = fromTouch;
+        }
+        
+        public void run() {
+            doRefreshProgress(mId, mProgress, mFromTouch);
+            // Put ourselves back in the cache when we are done
+            mRefreshProgressRunnable = this;
+        }
+        
+        public void setup(int id, int progress, boolean fromTouch) {
+            mId = id;
+            mProgress = progress;
+            mFromTouch = fromTouch;
+        }
+        
+    }
+    
+    private synchronized void doRefreshProgress(int id, int progress, boolean fromTouch) {
+        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, fromTouch);
+        }
+    }
+    
+    void onProgressRefresh(float scale, boolean fromTouch) {        
+    }
+
+    private synchronized void refreshProgress(int id, int progress, boolean fromTouch) {
+        if (mUiThreadId == Thread.currentThread().getId()) {
+            doRefreshProgress(id, progress, fromTouch);
+        } else {
+            RefreshProgressRunnable r;
+            if (mRefreshProgressRunnable != null) {
+                // Use cached RefreshProgressRunnable if available
+                r = mRefreshProgressRunnable;
+                // Uncache it
+                mRefreshProgressRunnable = null;
+                r.setup(id, progress, fromTouch);
+            } else {
+                // Make a new one
+                r = new RefreshProgressRunnable(id, progress, fromTouch);
+            }
+            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) 
+     */
+    public synchronized void setProgress(int progress) {
+        setProgress(progress, false);
+    }
+    
+    synchronized void setProgress(int progress, boolean fromTouch) {
+        if (mIndeterminate) {
+            return;
+        }
+
+        if (progress < 0) {
+            progress = 0;
+        }
+
+        if (progress > mMax) {
+            progress = mMax;
+        }
+
+        if (progress != mProgress) {
+            mProgress = progress;
+            refreshProgress(R.id.progress, mProgress, fromTouch);
+        }
+    }
+
+    /**
+     * <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)
+     */
+    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) 
+     */
+    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) {
+            super.invalidateDrawable(dr);
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        Drawable d = mCurrentDrawable;
+        if (d != null) {
+            // onDraw will translate the canvas so we draw starting at 0,0
+            d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, 
+                    h - mPaddingBottom - mPaddingTop);
+        }
+    }
+
+    @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 && mCurrentDrawable instanceof AnimationDrawable) {
+                ((AnimationDrawable)mCurrentDrawable).start();
+            }
+        }
+    }
+
+    @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));
+    }
+}
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..ed8df22
--- /dev/null
+++ b/core/java/android/widget/RadioGroup.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+        }
+    }
+
+    /**
+     * <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..845b542
--- /dev/null
+++ b/core/java/android/widget/RatingBar.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 and/or drag 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>
+ * 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 as well
+     * as changes that were initiated programmatically.
+     */
+    public interface OnRatingBarChangeListener {
+        
+        /**
+         * Notification that the rating has changed. Clients can use the
+         * fromTouch 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 fromTouch True if the rating change was initiated by a user's
+         *            touch gesture.
+         */
+        void onRatingChanged(RatingBar ratingBar, float rating, boolean fromTouch);
+
+    }
+
+    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;
+    }
+    
+    /**
+     * @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((int) (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 fromTouch) {
+        super.onProgressRefresh(scale, fromTouch);
+
+        // Keep secondary progress in sync with primary
+        updateSecondaryProgress(getProgress());
+        
+        if (!fromTouch) {
+            // Callback for non-touch 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 fromTouch) {
+        if (mOnRatingBarChangeListener != null) {
+            mOnRatingBarChangeListener.onRatingChanged(this, getRating(),
+                    fromTouch);
+        }
+    }
+
+    @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..91d5805
--- /dev/null
+++ b/core/java/android/widget/RelativeLayout.java
@@ -0,0 +1,950 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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, 0);
+        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
+     */
+    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
+     */
+    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();
+        }
+    }
+
+    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();
+        }
+    }
+
+    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 != 0) {
+            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.java b/core/java/android/widget/RemoteViews.java
new file mode 100644
index 0000000..54951b7
--- /dev/null
+++ b/core/java/android/widget/RemoteViews.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+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.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater.Filter;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+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(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 View.setVisibility
+     */
+    private class SetViewVisibility extends Action {
+        public SetViewVisibility(int id, int vis) {
+            viewId = id;
+            visibility = vis;
+        }
+        
+        public SetViewVisibility(Parcel parcel) {
+            viewId = parcel.readInt();
+            visibility = parcel.readInt();
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeInt(visibility);   
+        }
+        
+        @Override
+        public void apply(View root) {
+            View target = root.findViewById(viewId);
+            if (target != null) {
+                target.setVisibility(visibility);
+            }
+        }
+        
+        private int viewId;
+        private int visibility;
+        public final static int TAG = 0;
+    }
+    
+    /**
+     * Equivalent to calling TextView.setText
+     */
+    private class SetTextViewText extends Action {
+        public SetTextViewText(int id, CharSequence t) {
+            viewId = id;
+            text = t;
+        }
+        
+        public SetTextViewText(Parcel parcel) {
+            viewId = parcel.readInt();
+            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            TextUtils.writeToParcel(text, dest, flags);   
+        }
+        
+        @Override
+        public void apply(View root) {
+            TextView target = (TextView) root.findViewById(viewId);
+            if (target != null) {
+                target.setText(text);
+            }
+        }
+        
+        int viewId;
+        CharSequence text;
+        public final static int TAG = 1;
+    }
+
+    /**
+     * Equivalent to calling ImageView.setResource
+     */
+    private class SetImageViewResource extends Action {
+        public SetImageViewResource(int id, int src) {
+            viewId = id;
+            srcId = src;
+        }
+        
+        public SetImageViewResource(Parcel parcel) {
+            viewId = parcel.readInt();
+            srcId = parcel.readInt();
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeInt(srcId);   
+        }
+        
+        @Override
+        public void apply(View root) {
+            ImageView target = (ImageView) root.findViewById(viewId);
+            Drawable d = mContext.getResources().getDrawable(srcId);
+            if (target != null) {
+                target.setImageDrawable(d);
+            }
+        }
+        
+        int viewId;
+        int srcId;
+        public final static int TAG = 2;
+    }
+    
+    /**
+     * Equivalent to calling ImageView.setImageURI
+     */
+    private class SetImageViewUri extends Action {
+        public SetImageViewUri(int id, Uri u) {
+            viewId = id;
+            uri = u;
+        }
+        
+        public SetImageViewUri(Parcel parcel) {
+            viewId = parcel.readInt();
+            uri = Uri.CREATOR.createFromParcel(parcel);
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            Uri.writeToParcel(dest, uri);
+        }
+        
+        @Override
+        public void apply(View root) {
+            ImageView target = (ImageView) root.findViewById(viewId);
+            if (target != null) {
+                target.setImageURI(uri);
+            }
+        }
+        
+        int viewId;
+        Uri uri;
+        public final static int TAG = 3;
+    }
+
+    /**
+     * Equivalent to calling ImageView.setImageBitmap
+     */
+    private class SetImageViewBitmap extends Action {
+        public SetImageViewBitmap(int id, Bitmap src) {
+            viewId = id;
+            bitmap = src;
+        }
+
+        public SetImageViewBitmap(Parcel parcel) {
+            viewId = parcel.readInt();
+            bitmap = Bitmap.CREATOR.createFromParcel(parcel);
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            if (bitmap != null) {
+                bitmap.writeToParcel(dest, flags);
+            }
+        }
+
+        @Override
+        public void apply(View root) {
+            if (bitmap != null) {
+                ImageView target = (ImageView) root.findViewById(viewId);
+                Drawable d = new BitmapDrawable(bitmap);
+                if (target != null) {
+                    target.setImageDrawable(d);
+                }
+            }
+        }
+
+        int viewId;
+        Bitmap bitmap;
+        public final static int TAG = 4;
+    }
+    
+    /**
+     * Equivalent to calling Chronometer.setBase, Chronometer.setFormat,
+     * and Chronometer.start/stop.
+     */
+    private class SetChronometer extends Action {
+        public SetChronometer(int id, long base, String format, boolean running) {
+            this.viewId = id;
+            this.base = base;
+            this.format = format;
+            this.running = running;
+        }
+        
+        public SetChronometer(Parcel parcel) {
+            viewId = parcel.readInt();
+            base = parcel.readLong();
+            format = parcel.readString();
+            running = parcel.readInt() != 0;
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeLong(base);
+            dest.writeString(format);
+            dest.writeInt(running ? 1 : 0);
+        }
+        
+        @Override
+        public void apply(View root) {
+            Chronometer target = (Chronometer) root.findViewById(viewId);
+            if (target != null) {
+                target.setBase(base);
+                target.setFormat(format);
+                if (running) {
+                    target.start();
+                } else {
+                    target.stop();
+                }
+            }
+        }
+        
+        int viewId;
+        boolean running;
+        long base;
+        String format;
+
+        public final static int TAG = 5;
+    }
+    
+    /**
+     * Equivalent to calling ProgressBar.setMax, ProgressBar.setProgress and
+     * ProgressBar.setIndeterminate
+     */
+    private class SetProgressBar extends Action {
+        public SetProgressBar(int id, int max, int progress, boolean indeterminate) {
+            this.viewId = id;
+            this.progress = progress;
+            this.max = max;
+            this.indeterminate = indeterminate;
+        }
+        
+        public SetProgressBar(Parcel parcel) {
+            viewId = parcel.readInt();
+            progress = parcel.readInt();
+            max = parcel.readInt();
+            indeterminate = parcel.readInt() != 0;
+        }
+        
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeInt(progress);
+            dest.writeInt(max);
+            dest.writeInt(indeterminate ? 1 : 0);
+        }
+        
+        @Override
+        public void apply(View root) {
+            ProgressBar target = (ProgressBar) root.findViewById(viewId);
+            if (target != null) {
+                target.setIndeterminate(indeterminate);
+                if (!indeterminate) {
+                    target.setMax(max);
+                    target.setProgress(progress);
+                }
+            }
+        }
+        
+        int viewId;
+        boolean indeterminate;
+        int progress;
+        int max;
+
+        public final static int TAG = 6;
+    }
+
+    /**
+     * 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 SetViewVisibility.TAG:
+                    mActions.add(new SetViewVisibility(parcel));
+                    break;
+                case SetTextViewText.TAG:
+                    mActions.add(new SetTextViewText(parcel));
+                    break;
+                case SetImageViewResource.TAG:
+                    mActions.add(new SetImageViewResource(parcel));
+                    break;
+                case SetImageViewUri.TAG:
+                    mActions.add(new SetImageViewUri(parcel));
+                    break;
+                case SetImageViewBitmap.TAG:
+                    mActions.add(new SetImageViewBitmap(parcel));
+                    break;
+                case SetChronometer.TAG:
+                    mActions.add(new SetChronometer(parcel));
+                    break;
+                case SetProgressBar.TAG:
+                    mActions.add(new SetProgressBar(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) {
+        addAction(new SetViewVisibility(viewId, 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) {
+        addAction(new SetTextViewText(viewId, 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) {   
+        addAction(new SetImageViewResource(viewId, 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) {
+        addAction(new SetImageViewUri(viewId, 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
+     * 
+     * @hide pending API Council approval to extend the public API
+     */
+    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
+        addAction(new SetImageViewBitmap(viewId, 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 running True if you want the clock to be running, false if not.
+     */
+    public void setChronometer(int viewId, long base, String format, boolean running) {
+        addAction(new SetChronometer(viewId, base, format, running));
+    }
+    
+    /**
+     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
+     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
+     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
+     * 
+     * @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) {
+        addAction(new SetProgressBar(viewId, max, progress, indeterminate));
+    }
+    
+    /**
+     * 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..456d58d
--- /dev/null
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.
+     */
+    public ResourceCursorAdapter(Context context, int layout, Cursor c) {
+        super(context, c);
+        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 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..5df2b6d
--- /dev/null
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 (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 (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..23a27ac
--- /dev/null
+++ b/core/java/android/widget/ScrollView.java
@@ -0,0 +1,1213 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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>You should never use a ScrollView 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 ScrollView.
+ * 
+ * <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 {
+    private static final int ANIMATED_SCROLL_GAP = 250;
+
+    /**
+     * 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.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;
+
+    public ScrollView(Context context) {
+        super(context);
+        initScrollView();
+
+        setVerticalScrollBarEnabled(true);
+        setVerticalFadingEdgeEnabled(true);
+
+        TypedArray a = context.obtainStyledAttributes(R.styleable.View);
+
+        initializeScrollbars(a);
+        initializeFadingEdge(a);
+
+        a.recycle();
+    }
+
+    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 bottom = getChildAt(0).getBottom();
+        final int span = bottom - mScrollY - getHeight();
+        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);
+    }
+
+    @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 > ViewConfiguration.getTouchSlop()) {
+                    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.getMinimumFlingVelocity()) &&
+                        (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 X 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) {
+            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 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, this.getWidth(), child.getWidth());
+                mScrollY = clamp(y, this.getHeight(), child.getHeight());
+            } else {
+                mScrollX = x;
+                mScrollY = y;
+            }            
+            if (oldX != mScrollX || oldY != mScrollY) {
+                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+                postInvalidate();  // So we draw again
+            }
+        }
+    }
+
+    /**
+     * 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 (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;
+            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());
+
+        // note: until bug 1137695 is fixed, disable smooth scrolling for this api
+        return scrollToChildRect(rectangle, true);//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);
+    }
+
+    /**
+     * 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();
+        int bottom = getChildAt(getChildCount() - 1).getBottom();
+
+        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, this.getWidth(), child.getWidth());
+            y = clamp(y, this.getHeight(), 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..fbe5e57
--- /dev/null
+++ b/core/java/android/widget/Scroller.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.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 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/SeekBar.java b/core/java/android/widget/SeekBar.java
new file mode 100644
index 0000000..e87dc2d
--- /dev/null
+++ b/core/java/android/widget/SeekBar.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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. 
+ * 
+ * be notified of the user's actions.
+ * Clients of the Seekbar can attach a {@link SeekBar.OnSeekBarChangeListener} to
+ *
+ * @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 as well
+     * as changes that were initiated programmatically. 
+     */
+    public interface OnSeekBarChangeListener {
+        
+        /**
+         * Notification that the progress level has changed. Clients can use the fromTouch 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 fromTouch True if the progress change was initiated by a user's touch gesture.
+         */
+        void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
+    
+        /**
+         * 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 fromTouch) {
+        super.onProgressRefresh(scale, fromTouch);
+
+        if (mOnSeekBarChangeListener != null) {
+            mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromTouch);
+        }
+    }
+
+    /**
+     * 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..df52b69
--- /dev/null
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -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.
+ */
+
+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 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, int)} or {@link #setViewImage(ImageView, String)} is
+ * invoked. 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);
+        } 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 String[] from = mFrom;
+        final int[] to = mTo;
+        final int len = to.length;
+
+        for (int i = 0; i < len; i++) {
+            final View v = view.findViewById(to[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 (mViewBinder != null) {
+                    bound = mViewBinder.setViewValue(v, data, text);
+                }
+
+                if (!bound) {
+                    if (v instanceof TextView) {
+                        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..4d2fab3
--- /dev/null
+++ b/core/java/android/widget/SimpleCursorAdapter.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 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.
+ *
+ * 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
+     * @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 SimpleCursorAdapter(Context context, int layout, Cursor c,
+                               String[] from, int[] to) {
+        super(context, layout, c);
+        mTo = to;
+        mOriginalFrom = from;
+        findColumns(from);
+    }
+    
+    /**
+     * 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) {
+        for (int i = 0; i < mTo.length; i++) {
+            final View v = view.findViewById(mTo[i]);
+            if (v != null) {
+                String text = cursor.getString(mFrom[i]);
+                if (text == null) {
+                    text = "";
+                }
+
+                boolean bound = false;
+                if (mViewBinder != null) {
+                    bound = mViewBinder.setViewValue(v, cursor, mFrom[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);
+    }
+
+    private void findColumns(String[] from) {
+        int i;
+        int count = from.length;
+        if (mFrom == null) {
+            mFrom = new int[count];
+        }
+        if (mCursor != null) {
+            for (i = 0; i < count; i++) {
+                mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
+            }
+        } else {
+            for (i = 0; i < count; i++) {
+                mFrom[i] = -1;
+            }
+        }
+    }
+
+    @Override
+    public void changeCursor(Cursor c) {
+        super.changeCursor(c);
+        // rescan columns in case cursor layout is different
+        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/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..da4a077
--- /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 LabelAndIconIndicatorStategy(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 LabelAndIconIndicatorStategy implements IndicatorStrategy {
+
+        private final CharSequence mLabel;
+        private final Drawable mIcon;
+
+        private LabelAndIconIndicatorStategy(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..d72ffb1
--- /dev/null
+++ b/core/java/android/widget/TableLayout.java
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 || mStretchableColumns.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..bd5db33
--- /dev/null
+++ b/core/java/android/widget/TextView.java
@@ -0,0 +1,4866 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+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.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.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.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.UpdateLayout;
+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.ViewTreeObserver;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.widget.RemoteViews.RemoteView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import com.android.internal.util.FastMath;
+
+/**
+ * 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
+ */
+@RemoteView
+public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
+    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 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;
+
+    private Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
+    private int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
+    private int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
+    private boolean mDrawables;
+    private int mDrawablePadding;
+
+    private CharSequence mError;
+    private boolean mErrorWasChanged;
+    private PopupWindow mPopup;
+
+    private CharWrapper mCharWrapper = null;
+    private Rect mCompoundRect;
+
+    private boolean mSelectionMoved = false;
+
+    /* 
+     * 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");
+    }
+
+    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 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_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;
+            }
+        }
+        a.recycle();
+
+        BufferType bufferType = BufferType.EDITABLE;
+
+        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);
+            }
+        } else if (digits != null) {
+            mInput = DigitsKeyListener.getInstance(digits.toString());
+        } else if (phone) {
+            mInput = DialerKeyListener.getInstance();
+        } else if (numeric != 0) {
+            mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
+                                                   (numeric & DECIMAL) != 0);
+        } else if (autotext || autocap != -1) {
+            TextKeyListener.Capitalize cap;
+
+            switch (autocap) {
+            case 1:
+                cap = TextKeyListener.Capitalize.SENTENCES;
+                break;
+
+            case 2:
+                cap = TextKeyListener.Capitalize.WORDS;
+                break;
+
+            case 3:
+                cap = TextKeyListener.Capitalize.CHARACTERS;
+                break;
+
+            default:
+                cap = TextKeyListener.Capitalize.NONE;
+                break;
+            }
+
+            mInput = TextKeyListener.getInstance(autotext, cap);
+        } else if (editable) {
+            mInput = TextKeyListener.getInstance();
+        } else {
+            mInput = null;
+
+            switch (buffertype) {
+                case 0:
+                    bufferType = BufferType.NORMAL;
+                    break;
+                case 1:
+                    bufferType = BufferType.SPANNABLE;
+                    break;
+                case 2:
+                    bufferType = BufferType.EDITABLE;
+                    break;
+            }
+        }
+
+        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;
+        }
+
+        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.
+     */
+    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 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.
+     * <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) {
+        mInput = input;
+
+        if (mInput != null && !(mText instanceof Editable))
+            setText(mText);
+
+        setFilters((Editable) mText, mFilters);
+        fixFocusableAndClickableSettings();
+    }
+
+    /**
+     * @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 (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() {
+        if (mDrawableTop == null) {
+            return mPaddingTop;
+        } else {
+            return mPaddingTop + mDrawablePadding + mDrawableSizeTop;
+        }
+    }
+
+    /**
+     * Returns the bottom padding of the view, plus space for the bottom
+     * Drawable if any.
+     */
+    public int getCompoundPaddingBottom() {
+        if (mDrawableBottom == null) {
+            return mPaddingBottom;
+        } else {
+            return mPaddingBottom + mDrawablePadding + mDrawableSizeBottom;
+        }
+    }
+
+    /**
+     * Returns the left padding of the view, plus space for the left
+     * Drawable if any.
+     */
+    public int getCompoundPaddingLeft() {
+        if (mDrawableLeft == null) {
+            return mPaddingLeft;
+        } else {
+            return mPaddingLeft + mDrawablePadding + mDrawableSizeLeft;
+        }
+    }
+
+    /**
+     * Returns the right padding of the view, plus space for the right
+     * Drawable if any.
+     */
+    public int getCompoundPaddingRight() {
+        if (mDrawableRight == null) {
+            return mPaddingRight;
+        } else {
+            return mPaddingRight + mDrawablePadding + 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) {
+        mDrawableLeft = left;
+        mDrawableTop = top;
+        mDrawableRight = right;
+        mDrawableBottom = bottom;
+
+        mDrawables = mDrawableLeft != null
+                || mDrawableRight != null
+                || mDrawableTop != null
+                || mDrawableBottom != null;
+
+        if (mCompoundRect == null &&
+                (left != null || top != null || right != null || bottom != null)) {
+            mCompoundRect = new Rect();
+        }
+
+        final Rect compoundRect = mCompoundRect;
+        int[] state = null;
+        
+        if (mDrawables) {
+            state = getDrawableState();
+        }
+        
+        if (mDrawableLeft != null) {
+            mDrawableLeft.setState(state);
+            mDrawableLeft.copyBounds(compoundRect);
+            mDrawableSizeLeft = compoundRect.width();
+            mDrawableHeightLeft = compoundRect.height();
+        } else {
+            mDrawableSizeLeft = mDrawableHeightLeft = 0;
+        }
+
+        if (mDrawableRight != null) {
+            mDrawableRight.setState(state);
+            mDrawableRight.copyBounds(compoundRect);
+            mDrawableSizeRight = compoundRect.width();
+            mDrawableHeightRight = compoundRect.height();
+        } else {
+            mDrawableSizeRight = mDrawableHeightRight = 0;
+        }
+
+        if (mDrawableTop != null) {
+            mDrawableTop.setState(state);
+            mDrawableTop.copyBounds(compoundRect);
+            mDrawableSizeTop = compoundRect.height();
+            mDrawableWidthTop = compoundRect.width();
+        } else {
+            mDrawableSizeTop = mDrawableWidthTop = 0;
+        }
+
+        if (mDrawableBottom != null) {
+            mDrawableBottom.setState(state);
+            mDrawableBottom.copyBounds(compoundRect);
+            mDrawableSizeBottom = compoundRect.height();
+            mDrawableWidthBottom = compoundRect.width();
+        } else {
+            mDrawableSizeBottom = 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 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() {
+        return new Drawable[] {
+            mDrawableLeft, mDrawableTop, mDrawableRight, mDrawableBottom
+        };
+    }
+
+    /**
+     * 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) {
+        mDrawablePadding = pad;
+
+        invalidate();
+        requestLayout();
+    }
+
+    /**
+     * Returns the padding between the compound drawables and the text.
+     */
+    public int getCompoundDrawablePadding() {
+        return mDrawablePadding;
+    }
+
+    @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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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();
+        }
+
+        int[] state = getDrawableState();
+        if (mDrawableTop != null && mDrawableTop.isStateful()) {
+            mDrawableTop.setState(state);
+        }
+        if (mDrawableBottom != null && mDrawableBottom.isStateful()) {
+            mDrawableBottom.setState(state);
+        }
+        if (mDrawableLeft != null && mDrawableLeft.isStateful()) {
+            mDrawableLeft.setState(state);
+        }
+        if (mDrawableRight != null && mDrawableRight.isStateful()) {
+            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 null;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        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
+     */
+    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
+     * <a href="{@docRoot}reference/available-resources.html#stringresources">
+     * String Resources</a> for an example of setting formatted text in the XML resource file.
+     *
+     * @attr ref android.R.styleable#TextView_text
+     */
+    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)
+     */
+    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 = "";
+        }
+
+        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());
+            }
+        }
+
+        if (type == BufferType.EDITABLE || mInput != null) {
+            Editable t = mEditableFactory.newEditable(text);
+            text = t;
+
+            setFilters(t, mFilters);
+        } 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);
+    }
+
+    /**
+     * 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)));
+            }
+        }
+    }
+
+    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.
+     *
+     * @attr ref android.R.styleable#TextView_hint
+     */
+    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.
+     *
+     * @attr ref android.R.styleable#TextView_hint
+     */
+    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
+     */
+    public CharSequence getHint() {
+        return mHint;
+    }
+
+    /**
+     * 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.
+     */
+    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;
+        setCompoundDrawables(mDrawableLeft, mDrawableTop,
+                             icon, mDrawableBottom);
+
+        if (error == null) {
+            if (mPopup != null) {
+                if (mPopup.isShowing()) {
+                    mPopup.dismiss();
+                }
+
+                mPopup = null;
+            }
+        } else {
+            if (isFocused()) {
+                showError();
+            }
+        }
+    }
+
+    private void showError() {
+        if (mPopup == null) {
+            LayoutInflater inflater = LayoutInflater.from(getContext());
+            TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
+                    null);
+
+            mPopup = new PopupWindow(err, 200, 50);
+            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
+         */
+        
+        return getWidth() - mPopup.getWidth()
+                - getPaddingRight() - mDrawableSizeRight / 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();
+
+        int icontop = getCompoundPaddingTop() +
+                                 (vspace - mDrawableHeightRight) / 2;
+
+        /*
+         * The "2" is the distance between the point and the top edge
+         * of the background.
+         */
+
+        return icontop + mDrawableHeightRight - getHeight() - 2;
+    }
+    
+    private void hideError() {
+        if (mPopup != null) {
+            if (mPopup.isShowing()) {
+                mPopup.dismiss();
+            }
+        }
+    }
+
+    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);
+        }
+
+        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) {
+                mHighlightPath.computeBounds(sTempRect, false);
+
+                int left = getCompoundPaddingLeft();
+                int top = getExtendedPaddingTop() + getVerticalOffset(true);
+
+                invalidate((int) sTempRect.left + left,
+                           (int) sTempRect.top + top,
+                           (int) sTempRect.right + left + 1,
+                           (int) sTempRect.bottom + top + 1);
+            }
+        }
+    }
+
+    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 onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
+            final ViewTreeObserver observer = getViewTreeObserver();
+            if (observer != null) {
+                observer.removeOnPreDrawListener(this);
+                mPreDrawState = PREDRAW_NOT_REGISTERED;
+            }
+        }
+    }
+
+    @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;
+
+        if (mDrawables) {
+            /*
+             * 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 (mDrawableLeft != null) {
+                canvas.save();
+                canvas.translate(scrollX + mPaddingLeft,
+                                 scrollY + compoundPaddingTop +
+                                 (vspace - mDrawableHeightLeft) / 2);
+                mDrawableLeft.draw(canvas);
+                canvas.restore();
+            }
+
+            if (mDrawableRight != null) {
+                canvas.save();
+                canvas.translate(scrollX + right - left - mPaddingRight - mDrawableSizeRight,
+                         scrollY + compoundPaddingTop + (vspace - mDrawableHeightRight) / 2);
+                mDrawableRight.draw(canvas);
+                canvas.restore();
+            }
+
+            if (mDrawableTop != null) {
+                canvas.save();
+                canvas.translate(scrollX + compoundPaddingLeft + (hspace - mDrawableWidthTop) / 2,
+                        scrollY + mPaddingTop);
+                mDrawableTop.draw(canvas);
+                canvas.restore();
+            }
+
+            if (mDrawableBottom != null) {
+                canvas.save();
+                canvas.translate(scrollX + compoundPaddingLeft +
+                        (hspace - mDrawableWidthBottom) / 2,
+                         scrollY + bottom - top - mPaddingBottom - mDrawableSizeBottom);
+                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);
+        }
+
+        Path highlight = null;
+
+        //  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())) {
+            int start = Selection.getSelectionStart(mText);
+            int end = Selection.getSelectionEnd(mText);
+
+            if (mCursorVisible && start >= 0 && isEnabled()) {
+                if (mHighlightPath == null)
+                    mHighlightPath = new Path();
+
+                if (start == end) {
+                    if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
+                        < BLINK) {
+                        if (mHighlightPathBogus) {
+                            mHighlightPath.reset();
+                            mLayout.getCursorPath(start, 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(start, end, 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);
+            }
+        }
+        */
+
+        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;
+    }
+
+    /**
+     * 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) {
+        if (!isEnabled()) {
+            return super.onKeyDown(keyCode, event);
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+                if (mSingleLine && mInput != null) {
+                    return super.onKeyDown(keyCode, event);
+                }
+        }
+
+        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;
+
+            if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
+                if (mError != null && !mErrorWasChanged) {
+                    setError(null, null);
+                }
+                return true;
+            }
+        }
+
+        // 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)
+            if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
+                return true;
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (!isEnabled()) {
+            return super.onKeyUp(keyCode, event);
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+                if (mSingleLine && mInput != null) {
+                    /*
+                     * 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);
+    }
+
+    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) {
+        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();
+        }
+    }
+
+    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;
+            }
+
+            width = Math.max(width, mDrawableWidthTop);
+            width = Math.max(width, 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);
+
+        desired = Math.max(desired, mDrawableHeightLeft);
+        desired = Math.max(desired, 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();
+
+                    desired = Math.max(desired, mDrawableHeightLeft);
+                    desired = Math.max(desired, 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;
+        }
+    }
+
+    /**
+     * Returns true if anything changed.
+     */
+    private 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) {
+        top += getExtendedPaddingTop();
+        bottom += getExtendedPaddingTop();
+        h += getCompoundPaddingLeft();
+
+        if (line == 0)
+            top -= getExtendedPaddingTop();
+        if (line == mLayout.getLineCount() - 1)
+            bottom += getExtendedPaddingBottom();
+
+        r.set(h, top, h, 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
+     */
+    public void setSingleLine(boolean singleLine) {
+        mSingleLine = singleLine;
+
+        if (singleLine) {
+            setLines(1);
+            setHorizontallyScrolling(true);
+            setTransformationMethod(SingleLineTransformationMethod.
+                                    getInstance());
+        } else {
+            setMaxLines(Integer.MAX_VALUE);
+            setHorizontallyScrolling(false);
+            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();
+        }
+    }
+
+    /**
+     * Returns where, if anywhere, words that are longer than the view
+     * is wide should be ellipsized.
+     */
+    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
+     */
+    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
+     */
+    public void setCursorVisible(boolean visible) {
+        mCursorVisible = visible;
+        invalidate();
+
+        if (visible) {
+            makeBlink();
+        } else if (mBlink != null) {
+            mBlink.removeCallbacks(mBlink);
+        }
+    }
+
+    /**
+     * 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) {
+    }
+
+    /**
+     * Adds a TextWatcher to the list of those whose methods are called
+     * whenever this TextView's text changes.
+     */
+    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);
+            }
+        }
+    }
+
+    private 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);
+            }
+        }
+    }
+
+    private 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);
+            }
+        }
+    }
+
+    private class ChangeWatcher
+    extends Handler
+    implements TextWatcher, SpanWatcher {
+        public void beforeTextChanged(CharSequence buffer, int start,
+                                      int before, int after) {
+            TextView.this.sendBeforeTextChanged(buffer, start, before, after);
+        }
+
+        public void onTextChanged(CharSequence buffer, int start,
+                                  int before, int after) {
+            invalidate();
+
+            int curs = Selection.getSelectionStart(buffer);
+
+            if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
+                                 Gravity.BOTTOM) {
+                registerForPreDraw();
+            }
+
+            if (curs >= 0) {
+                mHighlightPathBogus = true;
+
+                if (isFocused()) {
+                    mShowCursor = SystemClock.uptimeMillis();
+                    makeBlink();
+                }
+            }
+
+            checkForResize();
+
+            TextView.this.sendOnTextChanged(buffer, start, before, after);
+            TextView.this.onTextChanged(buffer, start, before, after);
+        }
+
+        public void afterTextChanged(Editable buffer) {
+            TextView.this.sendAfterTextChanged(buffer);
+        }
+
+        private void spanChange(Spanned buf, Object what, int o, int n) {
+            // XXX Make the start and end move together if this ends up
+            // spending too much time invalidating.
+
+            if (what == Selection.SELECTION_END) {
+                mHighlightPathBogus = true;
+
+                if (!isFocused()) {
+                    mSelectionMoved = true;
+                }
+
+                if (o >= 0 || n >= 0) {
+                    invalidateCursor(Selection.getSelectionStart(buf), o, n);
+                    registerForPreDraw();
+
+                    if (isFocused()) {
+                        mShowCursor = SystemClock.uptimeMillis();
+                        makeBlink();
+                    }
+                }
+            }
+
+            if (what == Selection.SELECTION_START) {
+                mHighlightPathBogus = true;
+
+                if (!isFocused()) {
+                    mSelectionMoved = true;
+                }
+
+                if (o >= 0 || n >= 0) {
+                    invalidateCursor(Selection.getSelectionEnd(buf), o, n);
+                }
+            }
+
+            if (what instanceof UpdateLayout ||
+                what instanceof ParagraphStyle) {
+                invalidate();
+                mHighlightPathBogus = true;
+                checkForResize();
+            }
+
+            if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
+                mHighlightPathBogus = true;
+
+                if (Selection.getSelectionStart(buf) >= 0) {
+                    invalidateCursor();
+                }
+            }
+        }
+
+        public void onSpanChanged(Spannable buf,
+                                  Object what, int s, int e, int st, int en) {
+            spanChange(buf, what, s, st);
+        }
+
+        public void onSpanAdded(Spannable buf, Object what, int s, int e) {
+            spanChange(buf, what, -1, s);
+        }
+
+        public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
+            spanChange(buf, what, s, -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
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        mShowCursor = SystemClock.uptimeMillis();
+
+        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();
+            }
+        }
+
+        if (mTransformation != null) {
+            mTransformation.onFocusChanged(this, mText, focused, direction,
+                                           previouslyFocusedRect);
+        }
+
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+    }
+
+    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();
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final boolean superResult = super.onTouchEvent(event);
+
+        /*
+         * 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 && event.getAction() == MotionEvent.ACTION_UP) {
+            mEatTouchRelease = false;
+            return superResult;
+        }
+
+        if (mMovement != null && mText instanceof Spannable &&
+            mLayout != null) {
+            if (mMovement.onTouchEvent(this, (Spannable) mText, event)) {
+                return true;
+            }
+        }
+
+        return superResult;
+    }
+
+    @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 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 onMenu(ID_SELECT_ALL);
+            }
+
+            break;
+
+        case KeyEvent.KEYCODE_X:
+            if (canCut()) {
+                return onMenu(ID_CUT);
+            }
+
+            break;
+
+        case KeyEvent.KEYCODE_C:
+            if (canCopy()) {
+                return onMenu(ID_COPY);
+            }
+
+            break;
+
+        case KeyEvent.KEYCODE_V:
+            if (canPaste()) {
+                return onMenu(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 canCut() {
+        if (mText.length() > 0 && getSelectionStart() >= 0) {
+            if (mText instanceof Editable && mInput != null) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean canCopy() {
+        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;
+    }
+
+    @Override
+    protected void onCreateContextMenu(ContextMenu menu) {
+        super.onCreateContextMenu(menu);
+
+        if (!isFocused()) {
+            return;
+        }
+
+        MenuHandler handler = new MenuHandler();
+
+        if (canSelectAll()) {
+            menu.add(0, ID_SELECT_ALL, 0,
+                    com.android.internal.R.string.selectAll).
+                setOnMenuItemClickListener(handler).
+                setAlphabeticShortcut('a');
+        }
+
+        boolean selection = getSelectionStart() != getSelectionEnd();
+
+        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');
+        }
+
+        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');
+        }
+
+        if (canPaste()) {
+            menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+                    setOnMenuItemClickListener(handler).
+                    setAlphabeticShortcut('v');
+        }
+
+        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);
+            }
+        }
+    }
+
+    private static final int ID_SELECT_ALL = 101;
+    private static final int ID_CUT = 102;
+    private static final int ID_COPY = 103;
+    private static final int ID_PASTE = 104;
+    private static final int ID_COPY_URL = 105;
+
+    private class MenuHandler implements MenuItem.OnMenuItemClickListener {
+        public boolean onMenuItemClick(MenuItem item) {
+            return onMenu(item.getItemId());
+        }
+    }
+
+    private boolean onMenu(int id) {
+        int selStart = getSelectionStart();
+        int selEnd = getSelectionEnd();
+
+        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_CUT:
+                if (min == max) {
+                    min = 0;
+                    max = mText.length();
+                }
+
+                clip.setText(mTransformed.subSequence(min, max));
+                ((Editable) mText).delete(min, max);
+                return true;
+
+            case ID_COPY:
+                if (min == max) {
+                    min = 0;
+                    max = mText.length();
+                }
+
+                clip.setText(mTransformed.subSequence(min, max));
+                return true;
+
+            case ID_PASTE:
+                CharSequence paste = clip.getText();
+
+                if (paste != null) {
+                    Selection.setSelection((Spannable) mText, max);
+                    ((Editable) mText).replace(min, max, paste);
+                }
+
+                return true;
+
+            case ID_COPY_URL:
+                URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+                                                       URLSpan.class);
+                if (urls.length == 1) {
+                    clip.setText(urls[0].getURL());
+                }
+
+                return true;
+        }
+
+        return false;
+    }
+
+    public boolean performLongClick() {
+        if (super.performLongClick()) {
+            mEatTouchRelease = true;
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean mEatTouchRelease = false;
+
+    @ViewDebug.ExportedProperty
+    private CharSequence            mText;
+    private CharSequence            mTransformed;
+    private BufferType              mBufferType = BufferType.NORMAL;
+
+    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 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..da3c2aa
--- /dev/null
+++ b/core/java/android/widget/VideoView.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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);
+            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.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);
+                    }
+                    if (mStartWhenPrepared) {
+                        mMediaPlayer.start();
+                        if (mMediaController != null) {
+                            mMediaController.show();
+                        }
+                   } else if (!isPlaying() && (mSeekWhenPrepared != 0 || getCurrentPosition() > 0)) {
+                       if (mMediaController != null) {
+                           mMediaController.show(0);   // show the media controls when we're paused into a video and make 'em stick.
+                       }
+                   }
+                }
+            } else {
+                Log.d("VideoView", "Couldn't get video size after prepare(): " +
+                        mVideoWidth + "/" + mVideoHeight);
+                // The file was probably truncated or corrupt. Start anyway, so
+                // that we play whatever short snippet is there and then get
+                // the "playback completed" event.
+                if (mStartWhenPrepared) {
+                    mMediaPlayer.start();
+                }
+            }
+        }
+    };
+
+    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 (mIsPrepared && mVideoWidth == w && mVideoHeight == h) {
+                if (mSeekWhenPrepared != 0) {
+                    mMediaPlayer.seekTo(mSeekWhenPrepared);
+                }
+                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) {
+                if (mMediaPlayer.isPlaying()) {
+                    pause();
+                    mMediaController.show();
+                } else {
+                    start();
+                    mMediaController.hide();
+                }
+                return true;
+            } 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..acc9c46
--- /dev/null
+++ b/core/java/android/widget/ViewAnimator.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.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.
+ */
+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);
+        }
+    }
+
+    /**
+     * 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..a3c15d9
--- /dev/null
+++ b/core/java/android/widget/ViewFlipper.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * 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.
+ */
+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
+     */
+    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..5df8c8a
--- /dev/null
+++ b/core/java/android/widget/ZoomButton.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.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+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(new SimpleOnGestureListener() {
+            @Override
+            public void onLongPress(MotionEvent e) {
+                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/ZoomControls.java b/core/java/android/widget/ZoomControls.java
new file mode 100644
index 0000000..1fd662c
--- /dev/null
+++ b/core/java/android/widget/ZoomControls.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.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() {
+        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/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..53b9654
--- /dev/null
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -0,0 +1,860 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.text.TextUtils.TruncateAt;
+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.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 Button mButton1;
+
+    private CharSequence mButton1Text;
+
+    private Message mButton1Message;
+
+    private Button mButton2;
+
+    private CharSequence mButton2Text;
+
+    private Message mButton2Message;
+
+    private Button mButton3;
+
+    private CharSequence mButton3Text;
+
+    private Message mButton3Message;
+
+    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 == mButton1 && mButton1Message != null) {
+                m = Message.obtain(mButton1Message);
+            } else if (v == mButton2 && mButton2Message != null) {
+                m = Message.obtain(mButton2Message);
+            } else if (v == mButton3 && mButton3Message != null) {
+                m = Message.obtain(mButton3Message);
+            }
+            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.BUTTON1:
+                case DialogInterface.BUTTON2:
+                case DialogInterface.BUTTON3:
+                    ((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);
+    }
+    
+    public void installContent() {
+        /* We use a custom title so never request a window title */
+        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        
+        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 that dialog.
+     */
+    public void setView(View view) {
+        mView = view;
+    }
+
+    public void setButton(CharSequence text, Message msg) {
+        mButton1Text = text;
+        mButton1Message = msg;
+    }
+
+    public void setButton2(CharSequence text, Message msg) {
+        mButton2Text = text;
+        mButton2Message = msg;
+    }
+
+    public void setButton3(CharSequence text, Message msg) {
+        mButton3Text = text;
+        mButton3Message = 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.
+     */
+    public void setButton(CharSequence text, final DialogInterface.OnClickListener listener) {
+        mButton1Text = text;
+        if (listener != null) {
+            mButton1Message = mHandler.obtainMessage(DialogInterface.BUTTON1, listener);
+        } else {
+            mButton1Message = null;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public void setButton2(CharSequence text, final DialogInterface.OnClickListener listener) {
+        mButton2Text = text;
+        if (listener != null) {
+            mButton2Message = mHandler.obtainMessage(DialogInterface.BUTTON2, listener);
+        } else {
+            mButton2Message = null;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public void setButton3(CharSequence text, final DialogInterface.OnClickListener listener) {
+        mButton3Text = text;
+        if (listener != null) {
+            mButton3Message = mHandler.obtainMessage(DialogInterface.BUTTON3, listener);
+        } else {
+            mButton3Message = null;
+        }
+    }
+
+    /**
+     * 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 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, hasButtons);
+            
+        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));
+        } 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 hasButtons) {
+        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);
+                
+                /* The title font size and icon varies depending on 
+                 * what else is displayed within the dialog.
+                 */
+                if (mListView != null) {
+                    
+                    /* If a ListView is displayed then ensure the title 
+                     * is only 1 line and use a special icon.
+                     */
+                    mTitleView.setSingleLine();
+                    mTitleView.setEllipsize(TruncateAt.END);
+                    mIconView.setImageResource(
+                            R.drawable.ic_dialog_menu_generic);
+                } else if ((mMessage != null) && hasButtons) {
+                    
+                    /* Has a message and buttons, we want the title to
+                     * be a single line but large.
+                     */
+                    mTitleView.setSingleLine();
+                    mTitleView.setEllipsize(TruncateAt.END);
+                    mTitleView.setTextSize(getLargeTextSize());
+                } else {
+                    
+                    /* We have a Title and buttons or we have title,
+                     * and custom content. In either case the layout
+                     * handles it so do nothing.
+                     */
+                }
+                
+                /* 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 dissapear.
+                 */
+                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 int getLargeTextSize() {
+        TypedArray a =
+            mContext.obtainStyledAttributes(
+                    R.style.TextAppearance_Large,
+                    R.styleable.TextAppearance);
+        int textSize = a.getDimensionPixelSize(
+                R.styleable.TextAppearance_textSize, 22);
+        a.recycle();
+        return textSize;
+    }
+
+    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 BUTTON1 = 1;
+        int BUTTON2 = 2;
+        int BUTTON3 = 4;
+        int whichButton = 0;
+        mButton1 = (Button) mWindow.findViewById(R.id.button1);
+        mButton1.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButton1Text)) {
+            mButton1.setVisibility(View.GONE);
+        } else {
+            mButton1.setText(mButton1Text);
+            mButton1.setVisibility(View.VISIBLE);
+            defaultButton = mButton1;
+            whichButton = whichButton | BUTTON1;
+        }
+
+        mButton2 = (Button) mWindow.findViewById(R.id.button2);
+        mButton2.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButton2Text)) {
+            mButton2.setVisibility(View.GONE);
+        } else {
+            mButton2.setText(mButton2Text);
+            mButton2.setVisibility(View.VISIBLE);
+
+            if (defaultButton == null) {
+                defaultButton = mButton2;
+            }
+            whichButton = whichButton | BUTTON2;
+        }
+
+        mButton3 = (Button) mWindow.findViewById(R.id.button3);
+        mButton3.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButton3Text)) {
+            mButton3.setVisibility(View.GONE);
+        } else {
+            mButton3.setText(mButton3Text);
+            mButton3.setVisibility(View.VISIBLE);
+
+            if (defaultButton == null) {
+                defaultButton = mButton3;
+            }
+            whichButton = whichButton | BUTTON3;
+        }
+
+        /*
+         * If we only have 1 button it should be centered on the layout and
+         * expand to fill 50% of the available space.
+         */
+        if (whichButton == BUTTON1) {
+            centerButton(mButton1);
+        } else if (whichButton == BUTTON2) {
+            centerButton(mButton3);
+        } else if (whichButton == BUTTON3) {
+            centerButton(mButton3);
+        }
+        
+        return whichButton != 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 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(mPositiveButtonText, mPositiveButtonListener);
+            }
+            if (mNegativeButtonText != null) {
+                dialog.setButton2(mNegativeButtonText, mNegativeButtonListener);
+            }
+            if (mNeutralButtonText != null) {
+                dialog.setButton3(mNeutralButtonText, mNeutralButtonListener);
+            }
+            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) {
+                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) {
+    
+                        @Override
+                        public void bindView(View view, Context context,
+                                Cursor cursor) {
+                            CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
+                            text.setText(cursor.getString(cursor.getColumnIndexOrThrow(mLabelColumn)));
+                        }
+    
+                        @Override
+                        public View newView(Context context, Cursor cursor,
+                                ViewGroup parent) {
+                            View view = mInflater.inflate(
+                                    R.layout.select_dialog_multichoice, parent, false);
+                            bindView(view, context, cursor);
+                            if (cursor.getInt(cursor.getColumnIndexOrThrow(mIsCheckedColumn)) == 1) {
+                                listView.setItemChecked(cursor.getPosition(), true);
+                            }
+                            return view;
+                        }
+                        
+                    };
+                }
+            } 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/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
new file mode 100755
index 0000000..bb9fa0b
--- /dev/null
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -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 com.android.internal.app;
+
+interface IBatteryStats {
+    void noteStartWakelock(int uid, String name, int type);
+    void noteStopWakelock(int uid, String name, int type);
+    void setOnBattery(boolean onBattery);
+    long getAwakeTimeBattery();
+    long getAwakeTimePlugged();
+}
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..d5a9f8c
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.app.Activity;
+import android.app.ListActivity;
+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.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.Collections;
+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;
+    
+    @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);
+
+        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.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);
+                    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 ResolveListAdapter extends BaseAdapter {
+        private final Intent mIntent;
+        private final LayoutInflater mInflater;
+
+        private List<ResolveInfo> mList;
+        
+        public ResolveListAdapter(Context context, Intent intent) {
+            mIntent = new Intent(intent);
+            mIntent.setComponent(null);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+            PackageManager pm = context.getPackageManager();
+            mList = pm.queryIntentActivities(
+                    intent, PackageManager.MATCH_DEFAULT_ONLY
+                    | (mAlwaysCheck != null ? PackageManager.GET_RESOLVED_FILTER : 0));
+            if (mList != null) {
+                int N = mList.size();
+                if (N > 1) {
+                    // Only display the first matches that are either of equal
+                    // priority or have asked to be default options.
+                    ResolveInfo r0 = mList.get(0);
+                    for (int i=1; i<N; i++) {
+                        ResolveInfo ri = mList.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) {
+                                mList.remove(i);
+                                N--;
+                            }
+                        }
+                    }
+                    Collections.sort(mList, new ResolveInfo.DisplayNameComparator(pm));
+                }
+            }
+        }
+
+        public ResolveInfo resolveInfoForPosition(int position) {
+            if (mList == null) {
+                return null;
+            }
+
+            return mList.get(position);
+        }
+
+        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).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, ResolveInfo info) {
+            TextView text = (TextView)view.findViewById(com.android.internal.R.id.text1);
+            ImageView icon = (ImageView)view.findViewById(R.id.icon);
+
+            PackageManager pm = getPackageManager();
+
+            CharSequence label = info.loadLabel(pm);
+            if (label == null) label = info.activityInfo.name;
+            text.setText(label);
+            icon.setImageDrawable(info.loadIcon(pm));
+        }
+    }
+
+    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..63fe952
--- /dev/null
+++ b/core/java/com/android/internal/app/RingtonePickerActivity.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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();
+
+        // 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 (mHasSilentItem) {
+            mSilentPos = addSilentItem(listView);
+            
+            // The 'Silent' item should use a null Uri
+            if (mExistingUri == null) {
+                mClickedPos = mSilentPos;
+            }
+        }
+
+        if (mHasDefaultItem) {
+            mDefaultRingtonePos = addDefaultRingtoneItem(listView);
+            
+            if (RingtoneManager.isDefault(mExistingUri)) {
+                mClickedPos = mDefaultRingtonePos;
+            }
+        }
+        
+        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..5b2bde0
--- /dev/null
+++ b/core/java/com/android/internal/app/UsbStorageActivity.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mPositiveButtonListener = 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/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..bcbcc41
--- /dev/null
+++ b/core/java/com/android/internal/database/ArrayListCursor.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 com.android.internal.database;
+
+import android.database.AbstractCursor;
+
+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
+{
+    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(Long.valueOf(i));
+            }
+        }
+
+     }
+
+    @Override
+    public int getCount()
+    {
+        return mRows.length;
+    }
+
+    @Override
+    public boolean deleteRow()
+    {
+        return false;
+    }
+
+    @Override
+    public String[] getColumnNames()
+    {
+        return mColumnNames;
+    }
+
+    @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;
+    }
+
+    private String[] mColumnNames;
+    private ArrayList<Object>[] mRows;
+}
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/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..d8be889
--- /dev/null
+++ b/core/java/com/android/internal/logging/AndroidConfig.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.global.addHandler(new AndroidHandler());
+            Logger.global.setLevel(Level.ALL);
+        } 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..a6a4c64
--- /dev/null
+++ b/core/java/com/android/internal/logging/AndroidHandler.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * Implements a {@link java.util.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 SimpleFormatter();
+    
+    /**
+     * 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 (!Log.isLoggable(tag, level)) {
+                return;
+            }
+        
+            String msg;
+            try {
+                msg = getFormatter().format(record);
+            } catch (RuntimeException e) {
+                Log.e("AndroidHandler", "Error formatting log record", e);
+                msg = record.getMessage();
+            }
+            Log.println(level, tag, msg);
+        } catch (RuntimeException e) {
+            Log.e("AndroidHandler", "Error publishing log record", e);
+        }
+    }
+    
+    /**
+     * Converts a {@link java.util.Logger} logging level into an Android one.
+     * 
+     * @param level The {@link java.util.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/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/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/HandlerHelper.java b/core/java/com/android/internal/os/HandlerHelper.java
new file mode 100644
index 0000000..d810e6b
--- /dev/null
+++ b/core/java/com/android/internal/os/HandlerHelper.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 com.android.internal.os;
+
+import android.os.Handler;
+import android.os.HandlerInterface;
+import android.os.Message;
+
+/** @hide */
+public class HandlerHelper extends Handler
+{
+    public HandlerHelper(HandlerInterface target)
+    {
+        mTarget = target;
+    }
+
+    public void handleMessage(Message msg)
+    {
+        mTarget.handleMessage(msg);
+    }
+
+    private HandlerInterface mTarget;
+}
+
diff --git a/core/java/com/android/internal/os/HandlerThread.java b/core/java/com/android/internal/os/HandlerThread.java
new file mode 100644
index 0000000..1de6bfd
--- /dev/null
+++ b/core/java/com/android/internal/os/HandlerThread.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 com.android.internal.os;
+
+import android.os.Handler;
+import android.os.HandlerInterface;
+import android.os.Looper;
+
+/**
+ * Handy class for starting a new thread containing a Handler
+ * @hide
+ * @deprecated
+ */
+public class HandlerThread extends Thread
+{
+    Runnable mSetup;
+    HandlerInterface mhi;
+    Handler mh;
+    Throwable mtr;
+    final Object mMonitor = new Object();
+
+    public
+    HandlerThread(HandlerInterface h, Runnable setup, String name)
+    {
+        super(name);
+
+        mhi = h;
+        mSetup = setup;
+
+        synchronized (mMonitor) {
+            start();    
+            while (mh == null && mtr == null) {
+                try {
+                    mMonitor.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+
+        if (mtr != null) {
+            throw new RuntimeException("exception while starting", mtr);
+        }
+    }
+
+    @Override
+    public void
+    run()
+    {
+        synchronized(mMonitor) {
+            try {
+                Looper.prepare();
+                mh = new HandlerHelper (mhi);
+
+                if (mSetup != null) {
+                    mSetup.run();
+                    mSetup = null;
+                }
+            } catch (RuntimeException exc) {
+                mtr = exc;
+            }
+
+            mMonitor.notify();
+        }
+
+        if (mtr == null) {
+            Looper.loop();
+        }
+    }
+
+    public Handler
+    getHandler()
+    {
+        return mh;
+    }
+
+}
+
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/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..566332b
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.enableDebugger, 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 */
+        boolean enableDebugger;
+
+        /** 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")) {
+                    enableDebugger = true;
+                } 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.enableDebugger = true;
+        }
+    }
+
+    /**
+     * 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..5714b99
--- /dev/null
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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;
+                boolean gotMissingClass = false;
+                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);
+                        gotMissingClass = true;
+                    }
+                }
+
+                if (gotMissingClass &&
+                        "1".equals(SystemProperties.get("persist.service.adb.enable"))) {
+                    throw new IllegalStateException(
+                            "Missing class(es) for preloading, update preloaded-classes");
+                }
+
+                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 = 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)
+                                    + " that varies with configuration!!");
+                        }
+                    }
+                }
+                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();
+        }
+    }
+
+    /**
+     * 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=88161312,88161312",
+            "--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"
+             */
+            boolean debuggableBuild = "1".equals(SystemProperties.get("ro.debuggable"));
+            boolean enableDebugger = parsedArgs.enableDebugger || debuggableBuild;
+
+            /* Request to fork the system server process */
+            pid = Zygote.forkSystemServer(
+                    parsedArgs.uid, parsedArgs.gid,
+                    parsedArgs.gids, enableDebugger, 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/provider/Settings.java b/core/java/com/android/internal/provider/Settings.java
new file mode 100644
index 0000000..85ef17e
--- /dev/null
+++ b/core/java/com/android/internal/provider/Settings.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.provider;
+
+import android.provider.BaseColumns;
+import android.net.Uri;
+
+/**
+ * Settings related utilities.
+ */
+public class Settings {
+    /**
+     * Favorite intents
+     */
+    public static final class Favorites implements BaseColumns {
+        /**
+         * The content:// style URL for this table
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://" +
+                android.provider.Settings.AUTHORITY + "/favorites?notify=true");
+
+        /**
+         * The content:// style URL for this table. When this Uri is used, no notification is
+         * sent if the content changes.
+         */
+        public static final Uri CONTENT_URI_NO_NOTIFICATION =
+                Uri.parse("content://" + android.provider.Settings.AUTHORITY +
+                        "/favorites?notify=false");
+
+        /**
+         * The content:// style URL for a given row, identified by its id.
+         *
+         * @param id The row id.
+         * @param notify True to send a notification is the content changes.
+         *
+         * @return The unique content URL for the specified row.
+         */
+        public static Uri getContentUri(long id, boolean notify) {
+            return Uri.parse("content://" + android.provider.Settings.AUTHORITY +
+                    "/favorites/" + id + "?notify=" + notify);
+        }
+
+        /**
+         * The row ID.
+         * <p>Type: INTEGER</p>
+         */
+        public static final String ID = "_id";
+
+        /**
+         * Descriptive name of the favorite that can be displayed to the user.
+         * <P>Type: TEXT</P>
+         */
+        public static final String TITLE = "title";
+
+        /**
+         * The Intent URL of the favorite, 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";
+
+        /**
+         * The container holding the favorite
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CONTAINER = "container";
+
+        /**
+         * The icon is a resource identified by a package name and an integer id.
+         */
+        public static final int CONTAINER_DESKTOP = -100;
+
+        /**
+         * The screen holding the favorite (if container is CONTAINER_DESKTOP)
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SCREEN = "screen";
+
+        /**
+         * The X coordinate of the cell holding the favorite
+         * (if container is CONTAINER_DESKTOP or CONTAINER_DOCK)
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CELLX = "cellX";
+
+        /**
+         * The Y coordinate of the cell holding the favorite
+         * (if container is CONTAINER_DESKTOP)
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CELLY = "cellY";
+
+        /**
+         * The X span of the cell holding the favorite
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SPANX = "spanX";
+
+        /**
+         * The Y span of the cell holding the favorite
+         * <P>Type: INTEGER</P>
+         */
+        public static final String SPANY = "spanY";
+
+        /**
+         * The type of the favorite
+         *
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ITEM_TYPE = "itemType";
+
+        /**
+         * The favorite is an application
+         */
+        public static final int ITEM_TYPE_APPLICATION = 0;
+
+        /**
+         * The favorite is an application created shortcut
+         */
+        public static final int ITEM_TYPE_SHORTCUT = 1;
+
+        /**
+         * The favorite is a user created folder
+         */
+        public static final int ITEM_TYPE_USER_FOLDER = 2;
+
+        /**
+         * The favorite is a clock
+         */
+        public static final int ITEM_TYPE_WIDGET_CLOCK = 1000;
+
+        /**
+         * The favorite is a search widget
+         */
+        public static final int ITEM_TYPE_WIDGET_SEARCH = 1001;
+
+        /**
+         * The favorite is a photo frame
+         */
+        public static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002;
+
+        /**
+         * Indicates whether this favorite is an application-created shortcut or not.
+         * If the value is 0, the favorite is not an application-created shortcut, if the
+         * value is 1, it is an application-created shortcut.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String IS_SHORTCUT = "isShortcut";
+
+        /**
+         * The icon type.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ICON_TYPE = "iconType";
+
+        /**
+         * The icon is a resource identified by a package name and an integer id.
+         */
+        public static final int ICON_TYPE_RESOURCE = 0;
+
+        /**
+         * The icon is a bitmap.
+         */
+        public static final int ICON_TYPE_BITMAP = 1;
+
+        /**
+         * The icon package name, if icon type is ICON_TYPE_RESOURCE.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ICON_PACKAGE = "iconPackage";
+
+        /**
+         * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
+         * <P>Type: TEXT</P>
+         */
+        public static final String ICON_RESOURCE = "iconResource";
+
+        /**
+         * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
+         * <P>Type: BLOB</P>
+         */
+        public static final String ICON = "icon";
+    }
+}
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..b4dae94
--- /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;
+            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;
+            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/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..c16c165
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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);
+    }
+
+    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..156e20a
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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) {
+
+        mShortcutCaptionMode = 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;
+        }
+        
+        CharSequence text = mItemData.getTitleForItemView(this);
+        
+        if (shortcut) {
+            
+            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);
+
+            /*
+             * 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);
+        }
+    }
+
+    public void setItemInvoker(ItemInvoker itemInvoker) {
+        mItemInvoker = itemInvoker;
+    }
+
+    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..fc76a04
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 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;
+    
+    /** Item view for the 'More' button */
+    private IconMenuItemView mMoreItemView;
+    
+    /** Background of each item (should contain the selected and focused states) */
+    private Drawable mItemBackground;
+
+    /** Icon for the 'More' button */
+    private Drawable mMoreIcon;
+    
+    /** 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;
+    
+    /**
+     * 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);
+        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;
+        }
+        
+        // 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);
+    }
+    
+    /**
+     * Calculates the minimum number of rows needed to the items to be shown.
+     * @return the minimum number of rows
+     */
+    private int calculateNumberOfRows() {
+        return Math.min((int) Math.ceil(getChildCount() / (double) mMaxItemsPerRow), mMaxRows);
+    }
+
+    /**
+     * Adds an IconMenuItemView to this icon menu view.
+     * @param itemView The item's view to add
+     */
+    private void addItemView(IconMenuItemView itemView) {
+        ViewGroup.LayoutParams lp = 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);
+        
+        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 = mMaxItemsPerRow * mMaxRows;
+        // 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;
+        }
+    }
+
+    /**
+     * Calculates the number of items that should go on each row of this menu view.
+     * @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 index i contains the number of items for row i
+     */
+    private int[] calculateNumberOfItemsPerRow(final int numRows, final int numItems) {
+        // TODO: get from theme?  or write a best-fit algorithm? either way, this hard-coding needs
+        // to be dropped (946635).  Right now, this is according to UI spec.
+        final int numItemsForRow[] = new int[numRows];
+        if (numRows == 2) {
+            if (numItems <= 5) {
+                numItemsForRow[0] = 2;
+                numItemsForRow[1] = numItems - 2;
+            } else {
+                numItemsForRow[0] = numItemsForRow[1] = mMaxItemsPerRow;
+            }
+        } else if (numRows == 1) {
+            numItemsForRow[0] = numItems;
+        }
+        
+        return numItemsForRow;
+    }
+    
+    /**
+     * 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 = calculateNumberOfRows();
+        final int numRowsMinus1 = numRows - 1;
+        final int numItems = getChildCount();
+        final int numItemsForRow[] = calculateNumberOfItemsPerRow(numRows, numItems);
+        
+        // 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);
+        }
+        
+        // Get the desired height of the icon menu view (last row of items does
+        // not have a divider below)
+        final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) * calculateNumberOfRows()
+                - mHorizontalDividerHeight;
+        
+        // Maximum possible width and desired height
+        setMeasuredDimension(resolveSize(Integer.MAX_VALUE, widthMeasureSpec),
+                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;
+    }
+
+    @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 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);
+        }
+    }
+    
+    @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;
+        
+        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..e5d57ad
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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) {
+        mShortcutView.setText(mItemData.getShortcutLabel());
+
+        final int newVisibility = showShortcut ? VISIBLE : GONE;
+        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, 0);
+    }
+    
+    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, 0);
+    }
+
+    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..835e4a9
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 = true;
+    }
+    
+    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));
+            item.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();
+    }
+
+    public MenuItem get(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.
+     * 
+     * @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 = shortcutsVisible;
+        
+        refreshShortcuts(shortcutsVisible, 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))) {
+                    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])) {
+                    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.
+     */
+    public final void close(boolean allMenusAreClosing) {
+        Callback callback = getCallback();
+        if (callback != null) {
+            callback.onCloseMenu(this, allMenusAreClosing);
+        }
+    }
+
+    /**
+     * 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..6dfc7a2e
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.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 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_PANEL;
+        if (windowToken != null) {
+            lp.token = windowToken;
+        }
+        
+        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..c89f2e9
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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;
+    }
+
+    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());
+    }
+    
+    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);
+        if (oldFlags != mFlags) {
+            final int visibility = (shown) ? View.VISIBLE : View.GONE;
+            
+            for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
+                if (hasItemView(i)) {
+                    ((View) mItemViews[i].get()).setVisibility(visibility);
+                }
+            }
+         
+            return true;
+        }
+        
+        return false;
+    }
+    
+    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/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..1c75daa
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 ContentResolver mContentResolver;
+
+    private long mLockoutDeadline = 0;
+
+    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);
+    }
+
+    /**
+     * Store the lockout deadline, meaning the user can't attempt his/her unlock
+     * pattern until the deadline has passed.  Does not persist across reboots.
+     * @param deadline The elapsed real time in millis in future.
+     */
+    public void setLockoutAttemptDeadline(long deadline) {
+        mLockoutDeadline = 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() {
+        return (mLockoutDeadline <= SystemClock.elapsedRealtime()) ? 0 : mLockoutDeadline;
+    }
+
+    /**
+     * @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);
+    }
+
+
+}
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..bf00eff
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -0,0 +1,987 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 {
+    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 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;
+   
+
+    /**
+     * 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);
+
+        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;
+    }
+
+    /**
+     * 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 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);
+            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);
+    }
+
+    @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();
+    }
+
+    /**
+     * 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;
+
+        /**
+         * Constructor called from {@link LockPatternView#onSaveInstanceState()}
+         */
+        private SavedState(Parcelable superState, String serializedPattern, int displayMode,
+                boolean inputEnabled, boolean inStealthMode) {
+            super(superState);
+            mSerializedPattern = serializedPattern;
+            mDisplayMode = displayMode;
+            mInputEnabled = inputEnabled;
+            mInStealthMode = inStealthMode;
+        }
+
+        /**
+         * 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);
+        }
+        
+        public String getSerializedPattern() {
+            return mSerializedPattern;
+        }
+
+        public int getDisplayMode() {
+            return mDisplayMode;
+        }
+
+        public boolean isInputEnabled() {
+            return mInputEnabled;
+        }
+
+        public boolean isInStealthMode() {
+            return mInStealthMode;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeString(mSerializedPattern);
+            dest.writeInt(mDisplayMode);
+            dest.writeValue(mInputEnabled);
+            dest.writeValue(mInStealthMode);
+        }
+
+        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..5a7ddcb
--- /dev/null
+++ b/core/java/com/android/internal/widget/NumberPicker.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.ViewSwitcher;
+
+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, mSlideUpInAnimation, mSlideUpOutAnimation);
+                mHandler.postDelayed(this, mSpeed);
+            } else if (mDecrement) {
+                changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation);
+                mHandler.postDelayed(this, mSpeed);
+            }
+        }
+    };
+    
+    private final LayoutInflater mInflater;
+    private final TextView mText;
+    private final InputFilter mInputFilter;
+    private final InputFilter mNumberInputFilter;
+    
+    private final Animation mSlideUpOutAnimation;
+    private final Animation mSlideUpInAnimation;
+    private final Animation mSlideDownOutAnimation;
+    private final Animation mSlideDownInAnimation;
+    
+    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);
+    }
+
+    public NumberPicker(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs);
+        setOrientation(VERTICAL);
+        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        
+        mInflater.inflate(R.layout.number_picker, this, true);
+        mHandler = new Handler();
+        mInputFilter = 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[] { mInputFilter });
+        
+        mSlideUpOutAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
+                0, Animation.RELATIVE_TO_SELF, 0,
+                Animation.RELATIVE_TO_SELF, -100);
+        mSlideUpOutAnimation.setDuration(200);
+        mSlideUpInAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
+                0, Animation.RELATIVE_TO_SELF, 100,
+                Animation.RELATIVE_TO_SELF, 0);
+        mSlideUpInAnimation.setDuration(200);
+        mSlideDownOutAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
+                0, Animation.RELATIVE_TO_SELF, 0,
+                Animation.RELATIVE_TO_SELF, 100);
+        mSlideDownOutAnimation.setDuration(200);
+        mSlideDownInAnimation = new TranslateAnimation(
+                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
+                0, Animation.RELATIVE_TO_SELF, -100,
+                Animation.RELATIVE_TO_SELF, 0);
+        mSlideDownInAnimation.setDuration(200);
+        
+        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, mSlideUpInAnimation, mSlideUpOutAnimation);
+        } else if (R.id.decrement == v.getId()) {
+            changeCurrent(mCurrent - 1, mSlideDownInAnimation, mSlideDownOutAnimation);
+        }
+    }
+    
+    private String formatNumber(int value) {
+        return (mFormatter != null)
+                ? mFormatter.toString(value)
+                : String.valueOf(value);
+    }
+ 
+    private void changeCurrent(int current, Animation in, Animation out) {
+        
+        // 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 {
+
+        @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;
+    }
+
+}
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/SlidingDrawer.java b/core/java/com/android/internal/widget/SlidingDrawer.java
new file mode 100644
index 0000000..90a548a
--- /dev/null
+++ b/core/java/com/android/internal/widget/SlidingDrawer.java
@@ -0,0 +1,910 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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 com.android.internal.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;com.android.internal.widget.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;/com.android.internal.widget.SlidingDrawer&gt;
+ * </pre>
+ *
+ * @attr ref com.android.internal.R.styleable#SlidingDrawer_content
+ * @attr ref com.android.internal.R.styleable#SlidingDrawer_handle
+ * @attr ref com.android.internal.R.styleable#SlidingDrawer_topOffset
+ * @attr ref com.android.internal.R.styleable#SlidingDrawer_bottomOffset
+ * @attr ref com.android.internal.R.styleable#SlidingDrawer_orientation
+ * @attr ref com.android.internal.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 mAnimateOnClick;
+
+    /**
+     * 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);
+        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;
+
+        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();
+            mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        } else {
+            int width = widthSpecSize - handle.getMeasuredWidth();
+            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 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(VELOCITY_UNITS);
+
+                    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 > MAXIMUM_MINOR_VELOCITY) {
+                            xVelocity = MAXIMUM_MINOR_VELOCITY;
+                        }
+                    } else {
+                        negative = xVelocity < 0;
+                        if (yVelocity < 0) {
+                            yVelocity = -yVelocity;
+                        }
+                        if (yVelocity > MAXIMUM_MINOR_VELOCITY) {
+                            yVelocity = MAXIMUM_MINOR_VELOCITY;
+                        }
+                    }
+
+                    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) < MAXIMUM_TAP_VELOCITY) {
+                        if (vertical ? (mExpanded && top < TAP_THRESHOLD + mTopOffset) ||
+                                (!mExpanded && top > mBottomOffset + mBottom - mTop -
+                                        mHandleHeight - TAP_THRESHOLD) :
+                                (mExpanded && left < TAP_THRESHOLD + mTopOffset) ||
+                                (!mExpanded && left > mBottomOffset + mRight - mLeft -
+                                        mHandleWidth - TAP_THRESHOLD)) {
+
+                            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, 2000.0f, true);
+    }
+
+    private void animateOpen(int position) {
+        prepareTracking(position);
+        performFling(position, -2000.0f, true);
+    }
+
+    private void performFling(int position, float velocity, boolean always) {
+        mAnimationPosition = position;
+        mAnimatedVelocity = velocity;
+
+        if (mExpanded) {
+            if (always || (velocity > MAXIMUM_MAJOR_VELOCITY ||
+                    (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) &&
+                            velocity > -MAXIMUM_MAJOR_VELOCITY))) {
+                // We are expanded, but they didn't move sufficiently to cause
+                // us to retract.  Animate back to the expanded position.
+                mAnimatedAcceleration = MAXIMUM_ACCELERATION;
+                if (velocity < 0) {
+                    mAnimatedVelocity = 0;
+                }
+            } else {
+                // We are expanded and are now going to animate away.
+                mAnimatedAcceleration = -MAXIMUM_ACCELERATION;
+                if (velocity > 0) {
+                    mAnimatedVelocity = 0;
+                }
+            }
+        } else {
+            if (!always && (velocity > MAXIMUM_MAJOR_VELOCITY ||
+                    (position > (mVertical ? getHeight() : getWidth()) / 2 &&
+                            velocity > -MAXIMUM_MAJOR_VELOCITY))) {
+                // We are collapsed, and they moved enough to allow us to expand.
+                mAnimatedAcceleration = MAXIMUM_ACCELERATION;
+                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 = -MAXIMUM_ACCELERATION;
+                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 = MAXIMUM_ACCELERATION;
+            mAnimatedVelocity = 200;
+            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);
+
+        if (!mExpanded) {
+            return;
+        }
+
+        mExpanded = false;
+        if (mOnDrawerCloseListener != null) {
+            mOnDrawerCloseListener.onDrawerClosed();
+        }
+    }
+
+    private void openDrawer() {
+        moveHandle(EXPANDED_FULL_OPEN);
+        mContent.setVisibility(View.VISIBLE);
+        // TODO: Should we uncomment to preserve memory, but increase memory churn?
+        // mContent.destroyDrawingCache();
+
+        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;
+            }
+
+            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/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..48d0233
--- /dev/null
+++ b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
@@ -0,0 +1,424 @@
+// 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.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.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_GZIP = 
+            GoogleHttpClient.getGzipCapableUserAgent("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;
+        }
+    }
+
+    /**
+     * Creates a new AndroidGDataClient.
+     * 
+     * @param resolver The ContentResolver to get URL rewriting rules from
+     * through the Android proxy server, using null to indicate not using proxy.
+     */
+    public AndroidGDataClient(ContentResolver resolver) {
+        mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_GZIP);
+        mResolver = resolver;
+    }
+
+    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) {
+                return AndroidHttpClient.getUngzippedContent(entity);
+            }
+
+            // 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.");
+    }
+
+    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..acece47
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduComposer.java
@@ -0,0 +1,1164 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+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.MMS_VERSION_1_3);
+                } 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..3769349
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduHeaders.java
@@ -0,0 +1,720 @@
+/*
+ * 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.
+     */
+    // Current version is 1.3.
+    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);
+
+    /**
+     *  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 = MMS_VERSION_1_3; //1.3 is the default version.
+                }
+                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..4089c58
--- /dev/null
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -0,0 +1,1248 @@
+/*
+ * 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);
+                
+                boolean fakeRawAmr = contentType.equals("audio/amr");
+
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "Saving data to: " + uri);
+                }
+
+                byte[] buffer = new byte[256];
+                for (int len = 0; (len = is.read(buffer)) != -1; ) {
+                    if (fakeRawAmr && len > 32) {
+                        // This is a Gross Hack. We can only record audio to amr format in a 3gpp container.
+                        // Millions of handsets out there only support what is essentially raw AMR.
+                        // We work around this issue by extracting the AMR data out of the 3gpp container
+                        // (in a really stupid and non-portable way), prepending a little header, and then
+                        // using that as the attachment.
+                        // This also requires some cooperation from the SoundRecorder, which ends up saving
+                        // a "recording.amr" file with mime type audio/amr, even though it's a 3gpp file.
+                        if (buffer[4] ==  0x66 &&       // f
+                                buffer[5] == 0x74 &&    // t
+                                buffer[6] == 0x79 &&    // y
+                                buffer[7] == 0x70) {    // p
+                            byte [] amrHeader = new byte [] { 0x23, 0x21, 0x41, 0x4d, 0x52, 0x0a };
+                            os.write(amrHeader);
+                            os.write(buffer, 32, len - 32);
+                        } else {
+                            os.write(buffer, 0, len);
+                        }
+                        fakeRawAmr = false;
+                    } else {
+                        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..e2f1101
--- /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.MMS_VERSION_1_3);
+            // 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..bc6eaee
--- /dev/null
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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.impl.client.RequestWrapper;
+import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.os.SystemClock;
+import android.net.http.AndroidHttpClient;
+import android.provider.Checkin;
+import android.util.Config;
+import android.util.Log;
+
+import com.android.internal.os.RuntimeInit;
+
+/**
+ * {@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";
+
+    private final AndroidHttpClient mClient;
+    private final ContentResolver mResolver;
+    private final String mUserAgent;
+
+    /** 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;
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public GoogleHttpClient(ContentResolver resolver, String userAgent) {
+        mClient = AndroidHttpClient.newInstance(userAgent);
+        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 = 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.
+     */
+    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/NetStats.java b/core/java/com/google/android/net/NetStats.java
new file mode 100644
index 0000000..fee8219
--- /dev/null
+++ b/core/java/com/google/android/net/NetStats.java
@@ -0,0 +1,47 @@
+package com.google.android.net;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/**
+ * Gets network send/receive statistics for this process.
+ * The statistics come from /proc/pid/stat, using the ATOP kernel modification.
+ */
+public class NetStats {
+    private static String statsFile = "/proc/" + android.os.Process.myPid() + "/stat";
+
+    private static String TAG = "netstat";
+
+    /**
+     * Returns network stats for this process.
+     * Returns stats of 0 if problem encountered.
+     *
+     * @return [bytes sent, bytes received]
+     */
+    public static long[] getStats() {
+        long result[] = new long[2];
+
+        try {
+            BufferedReader br = new BufferedReader(new FileReader(statsFile), 512);
+            String line = br.readLine(); // Skip first line
+            line = br.readLine();
+            StringTokenizer st = new StringTokenizer(line);
+            st.nextToken(); // disk read
+            st.nextToken(); // disk sectors
+            st.nextToken(); // disk write
+            st.nextToken(); // disk sectors
+            st.nextToken(); // tcp send
+            result[0] = Long.parseLong(st.nextToken()); // tcp bytes sent
+            st.nextToken(); //tcp recv
+            result[1] = Long.parseLong(st.nextToken()); // tcp bytes recv
+        } catch (IOException e) {
+            // Probably wrong kernel; no point logging exception
+        } catch (NoSuchElementException e) {
+        } catch (NullPointerException e) {
+        }
+        return result;
+    }
+}
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..368b885
--- /dev/null
+++ b/core/java/com/google/android/net/ParentalControl.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.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 {
+    
+    /**
+     * 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) {
+        ICheckinService service =
+          ICheckinService.Stub.asInterface(ServiceManager.getService("checkin"));
+        
+        RemoteCallback remoteCallback = new RemoteCallback(callback);
+        try {
+            service.getParentalControlState(remoteCallback);
+        } 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/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..5709522
--- /dev/null
+++ b/core/java/com/google/android/util/GoogleWebContentHelper.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.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));
+        mPrettyUrl = fillUrl(Settings.Gservices.getString(contentResolver, prettySetting));
+        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;
+    }
+
+    /**
+     * 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) {
+        
+        if (TextUtils.isEmpty(url)) {
+            return "";
+        }
+        
+        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.progress);
+        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..95f2ddb
--- /dev/null
+++ b/core/java/com/google/android/util/SimplePullParser.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 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 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;
+        } 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;
+    }
+
+    /**
+     * 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;
+        } 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) {
+                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");
+        }
+    }
+
+    /**
+     * 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/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..e9009e6
--- /dev/null
+++ b/core/jni/Android.mk
@@ -0,0 +1,176 @@
+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
+
+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_NetStat.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_pim_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_AudioSystem.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_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, corecg graphics) \
+	$(call include-path-for, libhardware)/hardware \
+	$(LOCAL_PATH)/../../include/ui \
+	$(LOCAL_PATH)/../../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 \
+	libsgl \
+	libcorecg \
+	libsqlite \
+	libdvm \
+	libGLES_CM \
+	libhardware \
+	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
+
+ifeq ($(TARGET_ARCH),arm)
+LOCAL_SHARED_LIBRARIES += \
+	libdl
+endif
+
+LOCAL_LDLIBS += -lpthread -ldl
+
+ifeq ($(TARGET_OS),linux)
+ifeq ($(TARGET_ARCH),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..2a1184f
--- /dev/null
+++ b/core/jni/AndroidRuntime.cpp
@@ -0,0 +1,1151 @@
+/* //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_AudioSystem(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_pim_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_NetStat(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_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 from file.
+ */
+static void readLocale(char* language, char* region)
+{
+    char path[512];
+    char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX];
+    char *dataDir = getenv("ANDROID_DATA");
+    bool found = false;
+    if (dataDir && strlen(dataDir) < 500) {
+        strcpy(path, dataDir);
+    } else {
+        strcpy(path, "/data");
+    }
+    strcat(path, "/locale");
+    
+    FILE* localeFile = fopen(path, "r");
+    if (localeFile) {
+        char line[10];
+        char *got = fgets(line, 10, localeFile);
+        /* Locale code is ll_rr */
+        if (got != NULL && strlen(line) >= 5) {
+            strncat(language, line, 2);
+            strncat(region, line + 3, 2);
+            found = true;
+        } 
+        fclose(localeFile);
+    }
+
+    if (!found) {
+        /* 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 enableAssertBuf[4 + PROPERTY_VALUE_MAX];
+    char stackTraceFileBuf[PROPERTY_VALUE_MAX];
+    char* stackTraceFile = NULL;
+    char* slashClassName = NULL;
+    char* cp;
+    bool checkJni = false;
+    bool logStdio = false;
+    bool verifyJava = true;
+    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.verify-bytecode", propBuf, "");
+    if (strcmp(propBuf, "true") == 0) {
+        verifyJava = true;
+    } else if (strcmp(propBuf, "false") == 0) {
+        verifyJava = false;
+    } else {
+        /* bad value or not defined; use default */
+    }
+
+    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, "");
+
+    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";
+
+    /* limit memory use to 16MB */
+    opt.optionString = "-Xmx16m";
+    mOptions.add(opt);
+
+    /* enable Java "assert" statements in all non-system code */
+    //options[curOpt++].optionString = "-ea";
+
+    /*
+     * Enable or disable bytecode verification.  We currently force optimization
+     * to be enabled when the verifier is off; this is a bad idea, but
+     * useful while we fiddle with the verifier.
+     *
+     * This should be coordinated with:
+     * //device/dalvik/libcore/android/src/main/native/dalvik_system_TouchDex.cpp
+     */
+    if (verifyJava) {
+        opt.optionString = "-Xverify:all";
+        mOptions.add(opt);
+        opt.optionString = "-Xdexopt:verified";
+        mOptions.add(opt);
+    } else {
+        opt.optionString = "-Xverify:none";
+        mOptions.add(opt);
+        //opt.optionString = "-Xdexopt:all";
+        opt.optionString = "-Xdexopt:verified";
+        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 (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_pim_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_NetStat),
+    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_AudioSystem),
+    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_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..8628ec0
--- /dev/null
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -0,0 +1,573 @@
+#include "SkBitmap.h"
+#include "SkImageDecoder.h"
+#include "SkColorPriv.h"
+#include "GraphicsJNI.h"
+#include "SkDither.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;
+    }
+    
+    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, array, 0);
+    return true;
+}
+
+//////////////////// ToColor procs
+
+typedef void (*ToColorProc)(SkColor dst[], const void* src, int width,
+                            SkColorTable*);
+
+static inline SkColor pmcolorToColor(SkPMColor c) {
+    if (0 == c) {
+        return 0;
+    }
+
+    unsigned a = SkGetPackedA32(c);
+    unsigned r = SkGetPackedR32(c);
+    unsigned g = SkGetPackedG32(c);
+    unsigned b = SkGetPackedB32(c);
+
+    if (a < 255) {
+        SkFixed scale = SK_Fixed1 / a;
+        r = SkFixedRound(r * scale);
+        g = SkFixedRound(g * scale);
+        b = SkFixedRound(b * scale);
+        SkASSERT(r <= 0xFF);
+        SkASSERT(g <= 0xFF);
+        SkASSERT(b <= 0xFF);
+    }
+
+    return SkColorSetARGB(a, r, g, b);
+}
+
+static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width,
+                              SkColorTable*) {
+    SkASSERT(width > 0);
+    const SkPMColor* s = (const SkPMColor*)src;
+    do {
+        *dst++ = 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++ = 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++ = 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());
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#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 }
+};
+
+#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..90822a1
--- /dev/null
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -0,0 +1,505 @@
+#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;
+    jobject             fJOptions;  // java options object
+    SkImageDecoder*     fDecoder;
+};
+
+static SkMutex  gAutoDecoderCancelMutex;
+static AutoDecoderCancel* gAutoDecoderCancel;
+
+AutoDecoderCancel::AutoDecoderCancel(jobject joptions,
+                                       SkImageDecoder* decoder) {
+    fJOptions = joptions;
+    fDecoder = decoder;
+
+    // only need to be in the list if we have options
+    if (NULL != joptions) {
+        SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
+        
+        fNext = gAutoDecoderCancel;
+        gAutoDecoderCancel = this;
+    }
+}
+
+AutoDecoderCancel::~AutoDecoderCancel() {
+    const jobject joptions = fJOptions;
+    
+    if (NULL != joptions) {
+        SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
+        
+        // remove us
+        AutoDecoderCancel* pair = gAutoDecoderCancel;
+        AutoDecoderCancel* prev = NULL;
+        while (pair != NULL) {
+            AutoDecoderCancel* next = pair->fNext;
+            if (pair->fJOptions == joptions) {
+                SkASSERT(pair->fDecoder == fDecoder);
+                if (prev) {
+                    prev->fNext = next;
+                } else {
+                    gAutoDecoderCancel = next;
+                }
+                return;
+            }
+            pair = next;
+        }
+        SkDebugf("xxxxxxxxxxxxxxxxxxxxxxx not found in pair list %p %p\n",
+                 fJOptions, fDecoder);
+    }
+}
+
+bool AutoDecoderCancel::RequestCancel(jobject joptions) {
+    SkAutoMutexAcquire ac(gAutoDecoderCancelMutex);
+
+    AutoDecoderCancel* pair = gAutoDecoderCancel;
+    while (pair != NULL) {
+        if (pair->fJOptions == joptions) {
+            pair->fDecoder->cancelDecode();
+            return true;
+        }
+        pair = pair->fNext;
+    }
+    return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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);
+            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 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
+    }
+};
+
+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..841fe49
--- /dev/null
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -0,0 +1,934 @@
+/*
+ * 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,
+                                          SkCanvas* canvas, SkBitmap* bitmap,
+                                          jfloat left, jfloat top,
+                                          SkPaint* paint) {
+        SkScalar left_ = SkFloatToScalar(left);
+        SkScalar top_ = SkFloatToScalar(top);
+        canvas->drawBitmap(*bitmap, left_, top_, paint);
+    }
+
+    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,
+                                int x, int 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, SkIntToScalar(x), SkIntToScalar(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);
+
+        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","(IIFFI)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[IIIIIIIZI)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..002fdf9
--- /dev/null
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -0,0 +1,212 @@
+#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();
+            printf("------- reset threw an exception\n");
+            return false;
+        }
+        return true;
+    }
+    
+	virtual size_t read(void* buffer, size_t size) {
+        JNIEnv* env = fEnv;
+        
+        if (buffer == NULL && size == 0) {
+            jint avail = env->CallIntMethod(fJavaInputStream,
+                                            gInputStream_availableMethodID);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                printf("------- available threw an exception\n");
+                avail = 0;
+            }
+            return avail;
+        }
+
+        size_t bytesRead = 0;
+
+        if (buffer == NULL) { // skip
+            jlong skipped = env->CallLongMethod(fJavaInputStream,
+                                        gInputStream_skipMethodID, (jlong)size);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                printf("------- available threw an exception\n");
+                return 0;
+            }
+            if (skipped < 0) {
+                return 0;
+            }
+            return (size_t)skipped;
+        }
+
+        // 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();
+                printf("---- read threw an exception\n");
+                return 0;
+            }
+
+            if (n <= 0) {
+                break;  // eof
+            }
+
+            jbyte* array = env->GetByteArrayElements(fJavaByteArray, NULL);
+            memcpy(buffer, array, n);
+            env->ReleaseByteArrayElements(fJavaByteArray, array, 0);
+            
+            buffer = (void*)((char*)buffer + n);
+            bytesRead += n;
+            size -= n;
+            fBytesRead += n;
+        } while (size != 0);
+        
+        return bytesRead;
+    }
+    
+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;
+            }
+
+            jbyte* array = env->GetByteArrayElements(storage, NULL);
+            memcpy(array, buffer, requested);
+            env->ReleaseByteArrayElements(storage, array, 0);
+
+            fEnv->CallVoidMethod(fJavaOutputStream, gOutputStream_writeMethodID,
+                                 storage, 0, requested);
+            if (env->ExceptionCheck()) {
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                printf("------- 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..f8ccf72
--- /dev/null
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -0,0 +1,578 @@
+#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 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, gPointF_xFieldID, ir.fX);
+    env->SetIntField(obj, gPointF_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;
+}
+
+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");
+    
+    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..e67b20b
--- /dev/null
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -0,0 +1,156 @@
+#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);
+    
+    /** 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..5c48077
--- /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/ArrayIndexException");
+        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..a098d89
--- /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;
+        }
+        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(obj, 0);
+        if (array != NULL)
+        {
+            Res_png_9patch* chunk = (Res_png_9patch*)array;
+            int8_t numXDivs = chunk->numXDivs;
+            env->ReleasePrimitiveArrayCritical(obj, array, 0);
+            return array[0] != -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)
+    {
+        jbyte* array = env->GetByteArrayElements(chunkObj, 0);
+        if (array != NULL)
+        {
+            size_t chunkSize = env->GetArrayLength(chunkObj);
+            void* deserializedArray = alloca(chunkSize);
+            Res_png_9patch* chunk = (Res_png_9patch*) deserializedArray;
+            assert(chunkSize == ((Res_png_9patch*) array)->serializedSize());
+            memcpy(chunk, array, chunkSize);
+            Res_png_9patch::deserialize(chunk);            
+            NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
+            env->ReleaseByteArrayElements(chunkObj, array, 0);
+        }
+    } 
+
+    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);
+        jbyte* array = (jbyte*)env->GetByteArrayElements(chunkObj, 0);
+        if (array != NULL)
+        {
+            size_t chunkSize = env->GetArrayLength(chunkObj);
+            void* deserializedArray = alloca(chunkSize);
+            Res_png_9patch* chunk = (Res_png_9patch*) deserializedArray;
+            assert(chunkSize == ((Res_png_9patch*) array)->serializedSize());
+            memcpy(chunk, array, chunkSize);
+            Res_png_9patch::deserialize(chunk);
+            SkRegion* region = NULL;
+            NinePatch_Draw(NULL, bounds, *bitmap, *chunk, NULL, &region);
+            env->ReleaseByteArrayElements(chunkObj, array, 0);
+            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..ba63ae4
--- /dev/null
+++ b/core/jni/android/graphics/NinePatchImpl.cpp
@@ -0,0 +1,320 @@
+/*
+**
+** 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 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..05830de
--- /dev/null
+++ b/core/jni/android/graphics/Paint.cpp
@@ -0,0 +1,605 @@
+/* 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);
+
+        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);        
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        size_t textLength = env->GetArrayLength(text);
+    
+        if ((index | count) < 0 || (size_t)(index + count) > textLength) {
+            doThrow(env, "ArrayIndexOutOfBoundsException");
+            return 0;
+        }
+
+        jfloat width = SkScalarToFloat(paint->measureText(textArray + index, count << 1));
+        env->ReleaseCharArrayElements(text, textArray, 0);
+        return 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, "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) {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        count = dotextwidths(env, paint, textArray + index, count, widths);
+        env->ReleaseCharArrayElements(text, textArray, 0);        
+        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) {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
+        env->ReleaseCharArrayElements(text, textArray, 0);
+    }
+ 
+    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);
+        jchar* text = env->GetCharArrayElements(jtext, NULL);
+        count = breakText(env, *paint, text + index, count, maxWidth,
+                          jmeasuredWidth, tbd);
+        env->ReleaseCharArrayElements(jtext, text, 0);
+        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)
+    {
+        jchar* textArray = env->GetCharArrayElements(text, NULL);
+        doTextBounds(env, textArray + index, count, bounds, *paint);
+        env->ReleaseCharArrayElements(text, textArray, 0);
+    }
+    
+};
+
+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..71dc875
--- /dev/null
+++ b/core/jni/android/graphics/Shader.cpp
@@ -0,0 +1,273 @@
+#include <jni.h>
+#include "GraphicsJNI.h"
+
+#include "SkShader.h"
+#include "SkGradientShader.h"
+#include "SkPorterDuff.h"
+#include "SkShaderExtras.h"
+#include "SkTemplates.h"
+#include "SkXfermode.h"
+
+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)
+{
+    return SkShader::CreateBitmapShader(*bitmap,
+                                        (SkShader::TileMode)tileModeX,
+                                        (SkShader::TileMode)tileModeY);
+}
+    
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+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);
+    int*    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, (const SkColor*)colorValues,
+                                                      pos, count, (SkShader::TileMode)tileMode);
+    env->ReleaseIntArrayElements(colorArray, colorValues, 0);
+    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;
+
+    return SkGradientShader::CreateLinear(pts, colors, NULL, 2, (SkShader::TileMode)tileMode);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+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);
+    int*    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),
+                                                      (const SkColor*)colorValues, pos,
+                                                      count, (SkShader::TileMode)tileMode);
+    env->ReleaseIntArrayElements(colorArray, colorValues, 0);
+    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;
+
+    return SkGradientShader::CreateRadial(center, SkFloatToScalar(radius), colors, NULL,
+                                          2, (SkShader::TileMode)tileMode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkShader* SweepGradient_create1(JNIEnv* env, jobject, float x, float y,
+                                    jintArray jcolors, jfloatArray jpositions)
+{
+    size_t  count = env->GetArrayLength(jcolors);
+    int*    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),
+                                                     (const SkColor*)colors,
+                                                     pos, count);
+    env->ReleaseIntArrayElements(jcolors, colors, 0);
+    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;
+    return SkGradientShader::CreateSweep(SkFloatToScalar(x), SkFloatToScalar(y),
+                                         colors, NULL, 2);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+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..5cd2ceb
--- /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 <graphics/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..e77cd30
--- /dev/null
+++ b/core/jni/android_bluetooth_common.h
@@ -0,0 +1,137 @@
+/*
+** 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)); }
+
+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..66f0118
--- /dev/null
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -0,0 +1,464 @@
+/*
+ * 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"
+
+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 IF_LOGV() {
+        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..54e7de2
--- /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, "handle %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..9458433
--- /dev/null
+++ b/core/jni/android_database_SQLiteQuery.cpp
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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)
+{
+    int err;
+    sqlite3_stmt * statement = GET_STATEMENT(env, object);
+    int numRows = 0;
+    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(true) {
+        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
+                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());
+    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;II)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..b615895
--- /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, NULL);
+    }
+
+    // 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, NULL);
+    }
+
+    // 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, NULL);
+    }
+
+    // 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..a29c27b
--- /dev/null
+++ b/core/jni/android_hardware_Camera.cpp
@@ -0,0 +1,451 @@
+/*
+**
+** 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 "Camera-JNI"
+
+#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;
+    jfieldID    listener_context;
+    jmethodID   post_event;
+};
+
+static fields_t fields;
+
+struct callback_cookie {
+    jclass      camera_class;
+    jobject     camera_ref;
+};
+
+static Camera *get_native_camera(JNIEnv *env, jobject thiz)
+{
+    Camera *c = reinterpret_cast<Camera*>(env->GetIntField(thiz, fields.context));
+    if (c == 0)
+        jniThrowException(env, "java/lang/RuntimeException", "Method called after release()");
+
+    return c;
+}
+
+static void err_callback(status_t err, void *cookie)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    callback_cookie *c = (callback_cookie *)cookie;
+    int error;
+
+    switch (err) {
+    case DEAD_OBJECT:
+        error = kCameraErrorMediaServer;
+        break;
+    default:
+        error = kCameraErrorUnknown;
+        break;
+    }
+    LOGV("err_callback: camera_ref=%x, cookie=%x", (int)c->camera_ref, (int)cookie);
+    
+    env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                              c->camera_ref, kErrorCallback, error, 0, NULL);
+}
+
+// connect to camera service
+static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+    sp<Camera> c = Camera::connect();
+
+    if (c == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+        return;
+    }
+
+    // make sure camera hardware is alive
+    if (c->getStatus() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "Camera initialization failed");
+        return;
+    }
+
+    callback_cookie *cookie = new callback_cookie;
+    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;
+    }
+    cookie->camera_class = (jclass)env->NewGlobalRef(clazz);
+
+    // We use a weak reference so the Camera object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    cookie->camera_ref = env->NewGlobalRef(weak_this);
+    env->SetIntField(thiz, fields.listener_context, (int)cookie);
+
+    LOGV("native_setup: camera_ref=%x, camera_obj=%x, cookie=%x", (int)cookie->camera_ref, (int)thiz, (int)cookie);
+    
+    // save camera object in opaque field
+    env->SetIntField(thiz, fields.context, reinterpret_cast<int>(c.get()));
+
+    c->setErrorCallback(err_callback, cookie);
+    
+    // hold a strong reference so this doesn't go away while the app is still running
+    c->incStrong(thiz);
+}
+
+// disconnect from camera service
+static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
+{
+    sp<Camera> c = reinterpret_cast<Camera*>(env->GetIntField(thiz, fields.context));
+    // 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.
+    if (c != 0) {
+        c->disconnect();
+
+        // remove our strong reference created in native setup
+        c->decStrong(thiz);
+        env->SetIntField(thiz, fields.context, 0);
+        
+        callback_cookie *cookie = (callback_cookie *)env->GetIntField(thiz, fields.listener_context);
+
+        LOGV("release: camera_ref=%x, camera_obj=%x, cookie=%x", (int)cookie->camera_ref, (int)thiz, (int)cookie);
+      
+        if (cookie) {
+            env->DeleteGlobalRef(cookie->camera_ref);
+            env->DeleteGlobalRef(cookie->camera_class);
+            delete cookie;
+            env->SetIntField(thiz, fields.listener_context, 0);
+        }
+    }
+}
+
+static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, jobject surface)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+
+    sp<Surface> s = (Surface *)env->GetIntField(surface, fields.surface);
+    if (c->setPreviewDisplay(s) != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "setPreviewDisplay failed");
+        return;
+    }
+}
+
+static void preview_callback(const sp<IMemory>& mem, void *cookie)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    callback_cookie *c = (callback_cookie *)cookie;
+    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");
+        return;
+    }
+
+    jbyte *bytes = env->GetByteArrayElements(array, NULL);
+    memcpy(bytes, data, size);
+    env->ReleaseByteArrayElements(array, bytes, 0);
+
+    obj = array;
+
+    env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                              c->camera_ref, kPreviewCallback, arg1, arg2, obj);
+    env->DeleteLocalRef(array);
+}
+
+static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+    
+    if (c->startPreview() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "startPreview failed");
+        return;
+    }
+}
+
+static void android_hardware_Camera_stopPreview(JNIEnv *env, jobject thiz)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+
+    c->stopPreview();
+}
+
+static void android_hardware_Camera_setHasPreviewCallback(JNIEnv *env, jobject thiz, jboolean installed)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+
+    // 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.
+    callback_cookie *cookie = (callback_cookie *)env->GetIntField(thiz, fields.listener_context);
+    c->setFrameCallback(installed ? preview_callback : NULL, cookie);
+}
+
+static void autofocus_callback_impl(bool success, void *cookie)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    callback_cookie *c = (callback_cookie *)cookie;
+    env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                              c->camera_ref, kAutoFocusCallback, 
+                              success, 0, NULL);
+}
+
+
+
+static void android_hardware_Camera_autoFocus(JNIEnv *env, jobject thiz)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+    callback_cookie *cookie = (callback_cookie *)env->GetIntField(thiz, fields.listener_context);
+    c->setAutoFocusCallback(autofocus_callback_impl, cookie);
+    if (c->autoFocus() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "autoFocus failed");
+    }
+}
+
+static void jpeg_callback(const sp<IMemory>& mem, void *cookie)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    callback_cookie *c = (callback_cookie *)cookie;
+    int arg1 = 0, arg2 = 0;
+    jobject obj = NULL;
+
+    if (mem == NULL) {
+        env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                                  c->camera_ref, 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");
+        return;
+    }
+
+    jbyte *bytes = env->GetByteArrayElements(array, NULL);
+    memcpy(bytes, data, size);
+    env->ReleaseByteArrayElements(array, bytes, 0);
+
+    obj = array;
+
+    env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                              c->camera_ref, kJpegCallback, arg1, arg2, obj);
+    env->DeleteLocalRef(array);
+}
+
+static void shutter_callback_impl(void *cookie)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    callback_cookie *c = (callback_cookie *)cookie;
+    env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                              c->camera_ref, kShutterCallback, 0, 0, NULL);
+}
+
+static void raw_callback(const sp<IMemory>& mem __attribute__((unused)),
+                         void *cookie)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    callback_cookie *c = (callback_cookie *)cookie;
+    env->CallStaticVoidMethod(c->camera_class, fields.post_event,
+                              c->camera_ref, kRawCallback, 0, 0, NULL);
+}
+
+static void android_hardware_Camera_takePicture(JNIEnv *env, jobject thiz)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+
+    callback_cookie *cookie =
+        (callback_cookie *)env->GetIntField(thiz, fields.listener_context);
+    c->setShutterCallback(shutter_callback_impl, cookie);
+    c->setRawCallback(raw_callback, cookie);
+    c->setJpegCallback(jpeg_callback, cookie);
+    if (c->takePicture() != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "takePicture failed");
+        return;
+    }
+
+    return;
+}
+
+static void android_hardware_Camera_setParameters(JNIEnv *env, jobject thiz, jstring params)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return;
+
+    const jchar* str = env->GetStringCritical(params, 0);
+    String8 params8;
+    if (params) {
+        params8 = String8(str, env->GetStringLength(params));
+        env->ReleaseStringCritical(params, str);
+    }
+    if (c->setParameters(params8) != NO_ERROR) {
+        jniThrowException(env, "java/io/IllegalArgumentException", "setParameters failed");
+        return;
+    }
+}
+
+static jstring android_hardware_Camera_getParameters(JNIEnv *env, jobject thiz)
+{
+    Camera *c = get_native_camera(env, thiz);
+    if (c == 0)
+        return 0;
+
+    return env->NewStringUTF(c->getParameters().string());
+}
+
+//-------------------------------------------------
+
+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 },
+  { "setHasPreviewCallback",
+    "(Z)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 }
+};
+
+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/hardware/Camera", "mListenerContext", "I", &fields.listener_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..e8dcd71
--- /dev/null
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 {
+
+/*
+ * The method below are not thread-safe and not intended to be
+ */
+
+static jint
+android_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 sensors_data_open(fd); // doesn't take ownership of fd
+}
+
+static jint
+android_data_close(JNIEnv *env, jclass clazz)
+{
+    return sensors_data_close();
+}
+
+static jint
+android_data_poll(JNIEnv *env, jclass clazz, jfloatArray values, jint sensors)
+{
+    sensors_data_t data;
+    int res = sensors_data_poll(&data, sensors);
+    if (res) {
+        env->SetFloatArrayRegion(values, 0, 3, data.vector.v);
+        // return the sensor's number
+        res = 31 - __builtin_clz(res);
+        // and its status in the top 4 bits
+        res |= data.vector.status << 28;
+    }
+    return res;
+}
+
+static jint
+android_data_get_sensors(JNIEnv *env, jclass clazz)
+{
+    return sensors_data_get_sensors();
+}
+
+static JNINativeMethod gMethods[] = {
+    {"_sensors_data_open",  "(Ljava/io/FileDescriptor;)I",  (void*) android_data_open },
+    {"_sensors_data_close", "()I",   (void*) android_data_close },
+    {"_sensors_data_poll",  "([FI)I", (void*) android_data_poll },
+    {"_sensors_data_get_sensors","()I",   (void*) android_data_get_sensors },
+};
+
+}; // 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..264cc51
--- /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/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_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
new file mode 100644
index 0000000..99785a2
--- /dev/null
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -0,0 +1,186 @@
+/* //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));
+    if (int(type) == AudioTrack::VOICE_CALL) {
+        return check_AudioSystem_Command(AudioSystem::setStreamVolume(type, float(volume) / 100.0));
+    } else {
+        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) {
+        // voice call volume is converted to log scale in the hardware
+        if (int(type) == AudioTrack::VOICE_CALL) {
+            v_int = lrint(v * 100.0);
+        } else {
+            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_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp
new file mode 100644
index 0000000..73c1283
--- /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/OutOfMemoryException", 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..abd0961
--- /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/ArrayIndexOutOfBounds", 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/ArrayIndexOutOfBounds", 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..417ce54
--- /dev/null
+++ b/core/jni/android_net_NetUtils.cpp
@@ -0,0 +1,235 @@
+/*
+ * 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);
+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, jobject info)
+{
+    int result;
+
+    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+    result = ::dhcp_stop(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 },
+    { "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..48af99e
--- /dev/null
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -0,0 +1,492 @@
+/*
+ * 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 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_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) >= 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 },
+    { "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand },
+    { "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..6ba949c
--- /dev/null
+++ b/core/jni/android_os_Debug.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 int read_mapinfo(FILE *fp, stats_t* stats)
+{
+    char line[1024];
+    int len;
+    int skip;
+
+    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;
+
+    int isNativeHeap;
+    int isDalvikHeap;
+    int isSqliteHeap;
+
+again:
+    isNativeHeap = 0;
+    isDalvikHeap = 0;
+    isSqliteHeap = 0;
+    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) {
+        if (!strcmp(line + 49, "[heap]")) {
+            isNativeHeap = 1;
+        } else if (!strncmp(line + 49, "/dalvik-LinearAlloc", strlen("/dalvik-LinearAlloc"))) {
+            isDalvikHeap = 1;
+        } else if (!strncmp(line + 49, "/mspace/dalvik-heap", strlen("/mspace/dalvik-heap"))) {
+            isDalvikHeap = 1;
+        } else if (!strncmp(line + 49, "/dalvik-heap-bitmap/", strlen("/dalvik-heap-bitmap/"))) {
+            isDalvikHeap = 1;    
+        } else if (!strncmp(line + 49, "/tmp/sqlite-heap", strlen("/tmp/sqlite-heap"))) {
+            isSqliteHeap = 1;
+        }
+    }
+
+    // TODO: This needs to be fixed to be less fragile. If the order of this file changes or a new
+    // line is add, this method will return without filling out any of the information.
+
+    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, "Pss: %d kB", &pss) != 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 (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;
+    }
+    
+    return 1;
+}
+
+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;
+    
+    while (read_mapinfo(fp, stats) != 0) {
+        // Do nothing
+    }
+    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..a302498
--- /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/flashlight.h>
+#include <hardware/led.h>
+#include <hardware/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_NetStat.cpp b/core/jni/android_os_NetStat.cpp
new file mode 100644
index 0000000..983f719
--- /dev/null
+++ b/core/jni/android_os_NetStat.cpp
@@ -0,0 +1,158 @@
+/* //device/libs/android_runtime/android_os_Wifi.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.
+*/
+
+#define LOG_TAG "NetStat"
+
+#include "jni.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#if HAVE_ANDROID_OS
+#include <utils/Atomic.h>
+#endif
+
+namespace android {
+
+static jint android_os_netStatGetTxPkts(JNIEnv* env, jobject clazz)
+{
+  int ret = 0;
+  int fd = -1;
+  char input[50];
+  
+  fd = open("/sys/class/net/rmnet0/statistics/tx_packets", O_RDONLY);
+  if (fd <= 0) {
+    fd = open("/sys/class/net/ppp0/statistics/tx_packets", O_RDONLY);
+  }
+  
+  if (fd > 0) {
+    int size = read(fd, input, 50);
+    if (size > 0) {
+      ret = atoi(input);
+    }
+    close(fd);
+  }
+  
+  return (jint)ret;
+}
+
+static jint android_os_netStatGetRxPkts(JNIEnv* env, jobject clazz)
+{
+  int ret = 0;
+  int fd = -1;
+  char input[50];
+  
+  fd = open("/sys/class/net/rmnet0/statistics/rx_packets", O_RDONLY);
+  if (fd <= 0) {
+    fd = open("/sys/class/net/ppp0/statistics/rx_packets", O_RDONLY);
+  }
+  
+  if (fd > 0) {
+    int size = read(fd, input, 50);
+    if (size > 0) {
+      ret = atoi(input);
+    }
+    close(fd);
+  }
+  
+  return (jint)ret;
+}
+
+static jint android_os_netStatGetRxBytes(JNIEnv* env, jobject clazz)
+{
+  int ret = 0;
+  int fd = -1;
+  char input[50];
+  
+  fd = open("/sys/class/net/rmnet0/statistics/rx_bytes", O_RDONLY);
+  if (fd <= 0) {
+    fd = open("/sys/class/net/ppp0/statistics/rx_bytes", O_RDONLY);
+  }
+  
+  if (fd > 0) {
+    int size = read(fd, input, 50);
+    if (size > 0) {
+      ret = atoi(input);
+    }
+    close(fd);
+  }
+  
+  return (jint)ret;
+}
+
+
+static jint android_os_netStatGetTxBytes(JNIEnv* env, jobject clazz)
+{
+  int ret = 0;
+  int fd = -1;
+  char input[50];
+  
+  fd = open("/sys/class/net/rmnet0/statistics/tx_bytes", O_RDONLY);
+  if (fd <= 0) {
+    fd = open("/sys/class/net/ppp0/statistics/tx_bytes", O_RDONLY);
+  }
+  
+  if (fd > 0) {
+    int size = read(fd, input, 50);
+    if (size > 0) {
+      ret = atoi(input);
+    }
+    close(fd);
+  }
+  
+  return (jint)ret;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+
+    { "netStatGetTxPkts", "()I",
+      (void*) android_os_netStatGetTxPkts },
+
+    { "netStatGetRxPkts", "()I",
+      (void*) android_os_netStatGetRxPkts },
+
+    { "netStatGetTxBytes", "()I",
+      (void*) android_os_netStatGetTxBytes },
+
+    { "netStatGetRxBytes", "()I",
+      (void*) android_os_netStatGetRxBytes },
+
+};
+
+int register_android_os_NetStat(JNIEnv* env)
+{
+    jclass netStat = env->FindClass("android/os/NetStat");
+    LOG_FATAL_IF(netStat == NULL, "Unable to find class android/os/NetStat");
+
+    return AndroidRuntime::registerNativeMethods(env,
+            "android/os/NetStat", gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
+
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
new file mode 100644
index 0000000..465e233
--- /dev/null
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -0,0 +1,102 @@
+/* //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 const JNINativeMethod gParcelFileDescriptorMethods[] = {
+    {"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
+        (void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket}
+};
+
+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..02a4083
--- /dev/null
+++ b/core/jni/android_os_Power.cpp
@@ -0,0 +1,123 @@
+/* //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 "jni.h"
+#include "android_runtime/AndroidRuntime.h"
+#include <utils/misc.h>
+#include <hardware/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.
+    }
+#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..cac4372
--- /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/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_pim_Time.cpp b/core/jni/android_pim_Time.cpp
new file mode 100644
index 0000000..c1dd499
--- /dev/null
+++ b/core/jni/android_pim_Time.cpp
@@ -0,0 +1,594 @@
+/*
+** 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>
+
+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 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_pim_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_pim_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_pim_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_pim_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_pim_Time_format(JNIEnv* env, jobject This,
+                            jstring formatObject)
+{
+    Time t;
+    if (!java2time(env, &t, This)) return env->NewStringUTF("");
+    ACQUIRE_TIMEZONE(This, t)
+
+    const char* format = env->GetStringUTFChars(formatObject, NULL);
+
+    String8 r = t.format(format);
+
+    env->ReleaseStringUTFChars(formatObject, format);
+    RELEASE_TIMEZONE(This, t)
+
+    return env->NewStringUTF(r.string());
+}
+
+
+static jstring android_pim_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_pim_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_pim_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_pim_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 void android_pim_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;
+
+    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;
+    env->SetIntField(This, g_yearField, n);
+
+    n = get_char(env, s, 4, 10, &thrown);
+    n += get_char(env, s, 5, 1, &thrown);
+    n--;
+    if (thrown) return;
+    env->SetIntField(This, g_monField, n);
+
+    n = get_char(env, s, 6, 10, &thrown);
+    n += get_char(env, s, 7, 1, &thrown);
+    if (thrown) return;
+    env->SetIntField(This, g_mdayField, n);
+
+    if (len >= 15) {
+        env->SetBooleanField(This, g_allDayField, JNI_FALSE);
+        n = get_char(env, s, 9, 10, &thrown);
+        n += get_char(env, s, 10, 1, &thrown);
+        if (thrown) return;
+        env->SetIntField(This, g_hourField, n);
+
+        n = get_char(env, s, 11, 10, &thrown);
+        n += get_char(env, s, 12, 1, &thrown);
+        if (thrown) return;
+        env->SetIntField(This, g_minField, n);
+
+        n = get_char(env, s, 13, 10, &thrown);
+        n += get_char(env, s, 14, 1, &thrown);
+        if (thrown) return;
+        env->SetIntField(This, g_secField, n);
+    } 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);
+}
+
+static jboolean android_pim_Time_parse2445(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 {
+      // all day
+      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_pim_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_pim_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_pim_Time_normalize },
+    { "switchTimezone",          "(Ljava/lang/String;)V",                       (void*)android_pim_Time_switchTimezone },
+    { "compare",                 "(Landroid/pim/Time;Landroid/pim/Time;)I",     (void*)android_pim_Time_compare },
+    { "format",                  "(Ljava/lang/String;)Ljava/lang/String;",      (void*)android_pim_Time_format },
+    { "format2445",              "()Ljava/lang/String;",                        (void*)android_pim_Time_format2445 },
+    { "toString",                "()Ljava/lang/String;",                        (void*)android_pim_Time_toString },
+    { "parse",                   "(Ljava/lang/String;)V",                       (void*)android_pim_Time_parse },
+    { "nativeParse2445",         "(Ljava/lang/String;)Z",                       (void*)android_pim_Time_parse2445 },
+    { "nativeParse3339",         "(Ljava/lang/String;)Z",                       (void*)android_pim_Time_parse3339 },
+    { "setToNow",                "()V",                                         (void*)android_pim_Time_setToNow },
+    { "toMillis",                "(Z)J",                                        (void*)android_pim_Time_toMillis },
+    { "set",                     "(J)V",                                        (void*)android_pim_Time_set }
+};
+
+int register_android_pim_Time(JNIEnv* env)
+{
+    jclass timeClass = env->FindClass("android/pim/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;");
+
+    return AndroidRuntime::registerNativeMethods(env, "android/pim/Time", gMethods, NELEM(gMethods));
+}
+
+}; // 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_BluetoothDeviceService.cpp b/core/jni/android_server_BluetoothDeviceService.cpp
new file mode 100644
index 0000000..3ff6af1
--- /dev/null
+++ b/core/jni/android_server_BluetoothDeviceService.cpp
@@ -0,0 +1,1144 @@
+/*
+** 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 jboolean isConnectableNative(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, "IsConnectable",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean isDiscoverableNative(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, "IsDiscoverable",
+                           DBUS_TYPE_INVALID);
+        return reply ? dbus_returns_boolean(env, reply) : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+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 void common_Bonding(JNIEnv *env, jobject object, int timeout_ms,
+                           const char *func, jstring address) {
+#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, timeout_ms, nat->adapter,
+                                   DBUS_CLASS_NAME, func,
+                                   DBUS_TYPE_STRING, &c_address,
+                                   DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        if (reply) {
+            dbus_message_unref(reply);
+        }
+    }
+#endif
+}
+
+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 void cancelBondingProcessNative(JNIEnv *env, jobject object,
+                                       jstring address) {
+    LOGV(__FUNCTION__);
+    common_Bonding(env, object, -1, "CancelBondingProcess", address);
+}
+
+static void removeBondingNative(JNIEnv *env, jobject object, jstring address) {
+    LOGV(__FUNCTION__);
+    common_Bonding(env, object, -1, "RemoveBonding", address);
+}
+
+static jboolean hasBondingNative(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, "HasBonding",
+                           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 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 getMajorClassNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetMajorClass");
+}
+
+static jstring getMinorClassNative(JNIEnv *env, jobject obj) {
+    return common_Get(env, obj, "GetMinorClass");
+}
+
+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 getRemoteAliasNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteAlias", address);
+}
+
+static jboolean setRemoteAliasNative(JNIEnv *env, jobject obj,
+                                     jstring address, jstring alias) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, obj);
+    if (nat) {
+        const char *c_address = env->GetStringUTFChars(address, NULL);
+        const char *c_alias = env->GetStringUTFChars(alias, NULL);
+
+        LOGV("... address = %s alias = %s", c_address, c_alias);
+
+        DBusMessage *reply = dbus_func_args(env, nat->conn, nat->adapter,
+                                            DBUS_CLASS_NAME, "SetRemoteAlias",
+                                            DBUS_TYPE_STRING, &c_address,
+                                            DBUS_TYPE_STRING, &c_alias,
+                                            DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(address, c_address);
+        env->ReleaseStringUTFChars(alias, c_alias);
+        if (reply)
+        {
+            dbus_message_unref(reply);
+            return JNI_TRUE;
+        }
+        return JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jboolean clearRemoteAliasNative(JNIEnv *env, jobject obj, jstring address) {
+#ifdef HAVE_BLUETOOTH
+    LOGV(__FUNCTION__);
+    native_data_t *nat = get_native_data(env, obj);
+    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, "ClearRemoteAlias",
+                                            DBUS_TYPE_STRING, &c_address,
+                                            DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(address, c_address);
+        if (reply)
+        {
+            dbus_message_unref(reply);
+            return JNI_TRUE;
+        }
+        return JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+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 getRemoteMajorClassNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteMajorClass", address);
+}
+
+static jstring getRemoteMinorClassNative(JNIEnv *env, jobject obj, jstring address) {
+    return common_getRemote(env, obj, "GetRemoteMinorClass", 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 jobjectArray getRemoteServiceClassesNative(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, "GetRemoteServiceClasses",
+                           DBUS_TYPE_STRING, &c_address,
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(address, c_address);
+        return reply ? dbus_returns_array_of_strings(env, reply) : NULL;
+    }
+#endif
+    return NULL;
+}
+
+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
+    return bt_enable();
+#endif
+    return -1;
+}
+
+static jint disableNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    return bt_disable();
+#endif
+    return -1;
+}
+
+static jint isEnabledNative(JNIEnv *env, jobject object) {
+#ifdef HAVE_BLUETOOTH
+    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},
+    {"getMajorClassNative", "()Ljava/lang/String;", (void *)getMajorClassNative},
+    {"getMinorClassNative", "()Ljava/lang/String;", (void *)getMinorClassNative},
+    {"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},
+    {"hasBondingNative", "(Ljava/lang/String;)Z", (void *)hasBondingNative},
+    {"removeBondingNative", "(Ljava/lang/String;)Z", (void *)removeBondingNative},
+
+    {"getRemoteNameNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteNameNative},
+    {"getRemoteAliasNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteAliasNative},
+    {"setRemoteAliasNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)setRemoteAliasNative},
+    {"clearRemoteAliasNative", "(Ljava/lang/String;)Z", (void *)clearRemoteAliasNative},
+    {"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},
+    {"getRemoteMajorClassNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteMajorClassNative},
+    {"getRemoteMinorClassNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getRemoteMinorClassNative},
+    {"getRemoteServiceClassesNative", "(Ljava/lang/String;)[Ljava/lang/String;", (void *)getRemoteServiceClassesNative},
+    {"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..395a45c
--- /dev/null
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -0,0 +1,642 @@
+/*
+** 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_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_onRemoteAliasChanged;
+static jmethodID method_onRemoteAliasCleared;
+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;
+
+struct 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;
+};
+
+// Only valid during waitForAndDispatchEventNative()
+static 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_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_onRemoteAliasChanged = env->GetMethodID(clazz, "onRemoteAliasChanged", "(Ljava/lang/String;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;Z)V");
+
+    method_onPasskeyAgentRequest = env->GetMethodID(clazz, "onPasskeyAgentRequest", "(Ljava/lang/String;I)V");
+    method_onPasskeyAgentCancel = env->GetMethodID(clazz, "onPasskeyAgentCancel", "(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 jboolean add_adapter_event_match(JNIEnv *env, native_data_t *nat);
+static void remove_adapter_event_match(JNIEnv *env, native_data_t *nat);
+static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,
+                                      void *data);
+static DBusHandlerResult passkey_agent_event_filter(DBusConnection *conn,
+                                                    DBusMessage *msg,
+                                                    void *data);
+
+static const DBusObjectPathVTable passkey_agent_vtable = {
+    NULL, passkey_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);
+    if (nat != NULL && nat->conn != NULL) {
+        if (!dbus_connection_add_filter(nat->conn, event_filter, nat, NULL)){
+            return JNI_FALSE;
+        }
+
+        if (add_adapter_event_match(env, nat) != JNI_TRUE) {
+            return JNI_FALSE;
+        }
+
+        const char *path = "/android/bluetooth/PasskeyAgent";
+        if (!dbus_connection_register_object_path(nat->conn, path,
+                                                  &passkey_agent_vtable, NULL)) {
+            LOGE("%s: Can't register object path %s for agent!",
+                 __FUNCTION__, path);
+            return JNI_FALSE;
+        }
+
+        DBusError err;
+        dbus_error_init(&err);
+
+        // RegisterDefaultPasskeyAgent() will fail until hcid is up, so keep
+        // trying for 10 seconds.
+        int attempt;
+        for (attempt = 1000; attempt > 0; 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);
+                return JNI_TRUE;
+            } 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;
+            }
+        }
+        LOGE("Time-out trying to call RegisterDefaultPasskeyAgent(), "
+             "is hcid running?");
+        return JNI_FALSE;
+    }
+
+#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) {
+
+        const char *path = "/android/bluetooth/PasskeyAgent";
+        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);
+
+        DBusError err;
+        dbus_error_init(&err);
+        (void)dbus_connection_remove_filter(nat->conn,
+                                            event_filter,
+                                            nat);
+
+        dbus_connection_unregister_object_path(nat->conn, path);
+
+        remove_adapter_event_match(env, nat);
+    }
+#endif
+}
+
+#ifdef HAVE_BLUETOOTH
+static const char *const adapter_event_match =
+    "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".Adapter'";
+
+static jboolean add_adapter_event_match(JNIEnv *env, native_data_t *nat) {
+    if (nat == NULL || nat->conn == NULL) {
+        LOGE("%s: Not connected to d-bus!", __FUNCTION__);
+        return JNI_FALSE;
+    }
+    DBusError err;
+    dbus_error_init(&err);
+    dbus_bus_add_match(nat->conn, adapter_event_match, &err);
+    if (dbus_error_is_set(&err)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+static void remove_adapter_event_match(JNIEnv *env, native_data_t *nat) {
+    if (nat->conn == NULL) {
+        LOGE("%s: Not connected to d-bus!", __FUNCTION__);
+        return;
+    }
+    DBusError err;
+    dbus_error_init(&err);
+    dbus_bus_remove_match(nat->conn, adapter_event_match, &err);
+    if (dbus_error_is_set(&err)) {
+        LOG_AND_FREE_DBUS_ERROR(&err);
+    }
+}
+
+// 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", __FUNCTION__,
+         dbus_message_get_interface(msg), dbus_message_get_member(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",
+                                      "RemoteAliasChanged")) {
+        char *c_address, *c_alias;
+        if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_STRING, &c_address,
+                                  DBUS_TYPE_STRING, &c_alias,
+                                  DBUS_TYPE_INVALID)) {
+            LOGV("... address = %s, alias = %s", c_address, c_alias);
+            env->CallVoidMethod(nat->me,
+                                method_onRemoteAliasChanged,
+                                env->NewStringUTF(c_address),
+                                env->NewStringUTF(c_alias));
+        } 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",
+                                      "RemoteAliasCleared")) {
+        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_onRemoteAliasCleared,
+                                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 {
+        LOGV("... ignored");
+    }
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+// Called by dbus during WaitForAndDispatchEventNative()
+static DBusHandlerResult passkey_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));
+
+        return DBUS_HANDLER_RESULT_HANDLED;
+
+    } else if (dbus_message_is_method_call(msg,
+            "org.bluez.PasskeyAgent", "Release")) {
+        LOGE("We are no longer the passkey agent!");
+    } 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(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
+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);
+
+    jboolean result = JNI_TRUE;
+    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);
+        result = JNI_FALSE;
+        dbus_error_free(&err);
+    }
+
+    env->CallVoidMethod(event_loop_nat->me,
+                        method_onCreateBondingResult,
+                        env->NewStringUTF(address),
+                        result);
+    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", context->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..97daed3
--- /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/ArrayIndexException", 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/ArrayIndexException", 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_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
new file mode 100644
index 0000000..8a62159
--- /dev/null
+++ b/core/jni/android_util_AssetManager.cpp
@@ -0,0 +1,1673 @@
+/* //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;
+} 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)
+{
+    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);
+    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;
+    uint32_t typeSpecFlags;
+    ssize_t block = res.getResource(ident, &value, false, &typeSpecFlags);
+    uint32_t ref = ident;
+    if (resolve) {
+        block = res.resolveReference(&value, block, &ref);
+    }
+    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : 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* dest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
+    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, dest, 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* dest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
+    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, dest, 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* dest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
+    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, dest, 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");
+
+    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..24404a87
--- /dev/null
+++ b/core/jni/android_util_Binder.cpp
@@ -0,0 +1,1510 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+    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..d0cac18
--- /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 "utils/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..08c4f1c
--- /dev/null
+++ b/core/jni/android_util_Process.cpp
@@ -0,0 +1,743 @@
+/* //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_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},
+    {"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..f09bd4c
--- /dev/null
+++ b/core/jni/android_view_Surface.cpp
@@ -0,0 +1,619 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+        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);
+
+	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..843d293
--- /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 <graphics/SkCanvas.h>
+#include <graphics/SkDevice.h>
+#include <graphics/SkGLCanvas.h>
+#include <graphics/SkPaint.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..af03016
--- /dev/null
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -0,0 +1,475 @@
+/*
+**
+** 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 <GLES/egl.h>
+#include <GLES/gl.h>
+
+#include <ui/EGLNativeWindowSurface.h>
+#include <ui/Surface.h>
+#include <graphics/SkBitmap.h>
+#include <graphics/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->GetObjectArrayElement(configs, i);
+            if (obj == NULL) {
+                doThrow(_env, "java/lang/NullPointerException");
+                break;
+            }
+            _env->SetIntField(obj, gConfig_EGLConfigFieldID, (jint)nativeConfigs[i]);
+        }
+    }
+    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->NewString((const jchar *)chars,
+                            (jsize)strlen((const char *)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..1cd23b0
--- /dev/null
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -0,0 +1,6571 @@
+/* //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 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");
+
+    IAEClass = (jclass) _env->NewGlobalRef(IAEClassLocal);
+    OOMEClass = (jclass) _env->NewGlobalRef(OOMEClassLocal);
+    UOEClass = (jclass) _env->NewGlobalRef(UOEClassLocal);
+}
+
+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);
+    glDrawElements(
+        (GLenum)mode,
+        (GLsizei)count,
+        (GLenum)type,
+        (GLvoid *)indices
+    );
+    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 GLubyte * chars = glGetString((GLenum)name);
+
+    int len = strlen((const char *)chars);
+    jchar * wchars = (jchar *)malloc(len * sizeof(jchar));
+    if (wchars == (jchar*) 0) {
+        _env->ThrowNew(OOMEClass, "No space for glGetString output");
+        return (jstring) 0;
+    }
+    // Copy bytes -> chars, including trailing '\0'
+    for (int i = 0; i <= len; i++) {
+        wchars[i] = (jchar) chars[i];
+    }
+    jstring output = _env->NewString(wchars, (jsize) len);
+    free(wchars);
+    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..d108330
--- /dev/null
+++ b/core/jni/server/Android.mk
@@ -0,0 +1,37 @@
+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 \
+	libnativehelper \
+    libsystem_server \
+	libutils \
+	libui
+
+ifeq ($(TARGET_OS),linux)
+ifeq ($(TARGET_ARCH),x86)
+LOCAL_LDLIBS += -lpthread -ldl -lrt
+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..a81a0ff4
--- /dev/null
+++ b/core/jni/server/com_android_server_AlarmManagerService.cpp
@@ -0,0 +1,120 @@
+/* //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_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},
+};
+
+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..479e57d
--- /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/vibrator.h>
+
+namespace android
+{
+
+static void on(JNIEnv *env, jobject clazz)
+{
+    // LOGI("on\n");
+    vibrator_on();
+}
+
+static void off(JNIEnv *env, jobject clazz)
+{
+    // LOGI("off\n");
+    vibrator_off();
+}
+
+static JNINativeMethod method_table[] = {
+    { "on", "()V", (void*)on },
+    { "off", "()V", (void*)off }
+};
+
+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..4e9ffb1
--- /dev/null
+++ b/core/jni/server/com_android_server_KeyInputQueue.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+}
+
+
+// ----------------------------------------------------------------------------
+
+/*
+ * 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 },
+};
+
+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..37f6231
--- /dev/null
+++ b/core/jni/server/com_android_server_SensorService.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 jint
+android_init(JNIEnv *env, jclass clazz)
+{
+    return sensors_control_init();
+}
+
+static jobject
+android_open(JNIEnv *env, jclass clazz)
+{
+    int fd = sensors_control_open();
+    // 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)
+{
+    uint32_t active = sensors_control_activate(activate ? sensor : 0, sensor);
+    return (activate && !active) ? false : true;
+}
+
+static jint
+android_set_delay(JNIEnv *env, jclass clazz, jint ms)
+{
+    return sensors_control_delay(ms);
+}
+
+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_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..104ba88
--- /dev/null
+++ b/core/res/AndroidManifest.xml
@@ -0,0 +1,933 @@
+<?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">
+
+    <!-- ====================================== -->
+    <!-- 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" />
+
+    <!-- ======================================= -->
+    <!-- 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="normal"
+        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 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 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="signature" />
+
+    <!-- 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 the collected battery statistics -->
+    <permission android:name="android.permission.BATTERY_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" />
+
+    <!-- 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" />
+
+    <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" />
+
+    <application android:process="system"
+                 android:persistent="true"
+                 android:hasCode="false"
+                 android:label="Android System"
+                 android:allowClearUserData="false"
+                 android:icon="@drawable/ic_launcher_android">
+        <activity android:name="com.android.internal.app.ChooserActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                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: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">
+        </activity>
+        <provider android:name=".server.checkin.CheckinProvider"
+                android:authorities="android.server.checkin"
+                android:multiprocess="false" />
+        <provider android:name=".content.SyncProvider"
+                android:authorities="sync" android:multiprocess="false" />
+
+        <service android:name="com.android.server.LoadAverageService"
+            android:exported="true" />
+        <receiver
+            android:name=".server.checkin.UpdateReceiver$DownloadCompletedBroadcastReceiver"
+            android:exported="true" />
+
+        <receiver android:name=".server.checkin.SecretCodeReceiver">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SECRET_CODE" />
+                <data android:scheme="android_secret_code"
+                        android:host="682" />  <!-- 682 = "OTA" on dialpad -->
+                <data android:scheme="android_secret_code"
+                        android:host="682364" />  <!-- 682364 = "OTAENG" -->
+                <data android:scheme="android_secret_code"
+                        android:host="682226" />  <!-- 682226 = "OTACAN" -->
+                <data android:scheme="android_secret_code"
+                        android:host="682668" />  <!-- 682668 = "OTANOT" -->
+            </intent-filter>
+        </receiver>
+
+        <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.BrickReceiver"
+            android:permission="android.permission.BRICK" >
+            <intent-filter>
+                <action android:name="android.intent.action.BRICK" />
+            </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/film.png b/core/res/assets/webkit/film.png
new file mode 100755
index 0000000..f457f23
--- /dev/null
+++ b/core/res/assets/webkit/film.png
Binary files differ
diff --git a/core/res/assets/webkit/loaderror.html b/core/res/assets/webkit/loaderror.html
new file mode 100644
index 0000000..359a1e7
--- /dev/null
+++ b/core/res/assets/webkit/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/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/nodomain.html b/core/res/assets/webkit/nodomain.html
new file mode 100644
index 0000000..7a107fb
--- /dev/null
+++ b/core/res/assets/webkit/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/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/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..86e492a
--- /dev/null
+++ b/core/res/assets/webkit/youtube.html
@@ -0,0 +1,26 @@
+<html>
+  <head>
+    <style type="text/css">
+      body      { background-color: black; }
+      a:hover   { text-decoration: none; }
+      a:link    { color: white; }
+      a:visited { color: white; }
+    </style>
+  </head>
+  <body>
+    <table height="100%" width="100%">
+      <tr>
+        <td align="center" valign="middle" height="100%">
+          <img src="film.png"/>
+          <br/>
+          <a id="url" href="" onClick="javascript:window.top.document.location='http://youtube.com/watch' +
+            document.location.search; return false" target="_top">Open YouTube player</a>
+        </td>
+      </tr>
+      <tr><td valign="bottom" align="right">
+          <img src="youtube.png" style="opacity:.7"/>
+        </td>
+      </tr>
+    </table>
+  </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/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/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/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/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/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..7309656
--- /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="50%" 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..ca8a7e6
--- /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="50%" 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..7e61fc89
--- /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="#808080"/>
+    <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..9e82f75
--- /dev/null
+++ b/core/res/res/drawable-land/statusbar_background.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_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..47924a3
--- /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..f131eea
--- /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..00ec08e
--- /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_longpress.png b/core/res/res/drawable/btn_check_off_longpress.png
new file mode 100644
index 0000000..2117113
--- /dev/null
+++ b/core/res/res/drawable/btn_check_off_longpress.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..24793cd
--- /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..c2aa44da
--- /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..29764ec
--- /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..9ff0072
--- /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..2b31a0e
--- /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_longpress.png b/core/res/res/drawable/btn_check_on_longpress.png
new file mode 100644
index 0000000..6337033
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on_longpress.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..9c1f0eb
--- /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..21745aa
--- /dev/null
+++ b/core/res/res/drawable/btn_check_on_selected.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_default_trackball_selected.png b/core/res/res/drawable/btn_code_lock_default_trackball_selected.png
new file mode 100755
index 0000000..2e696c6
--- /dev/null
+++ b/core/res/res/drawable/btn_code_lock_default_trackball_selected.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_longpress.9.png b/core/res/res/drawable/btn_default_longpress.9.png
new file mode 100644
index 0000000..ba1a880
--- /dev/null
+++ b/core/res/res/drawable/btn_default_longpress.9.png
Binary files differ
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..6644ad0
--- /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..27d98a9
--- /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..b36b2d3
--- /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..0fca4d9
--- /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..a3f756e
--- /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_longpress.9.png b/core/res/res/drawable/btn_default_small_longpress.9.png
new file mode 100644
index 0000000..15df06f
--- /dev/null
+++ b/core/res/res/drawable/btn_default_small_longpress.9.png
Binary files differ
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..6726b04
--- /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..2ead262
--- /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..d60370b
--- /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..04cdc64
--- /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..0a2b9e9
--- /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_disable_focused.png b/core/res/res/drawable/btn_dialog_disable_focused.png
new file mode 100755
index 0000000..23545ca
--- /dev/null
+++ b/core/res/res/drawable/btn_dialog_disable_focused.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_disabled.9.png b/core/res/res/drawable/btn_dropdown_disabled.9.png
new file mode 100644
index 0000000..dc6e679
--- /dev/null
+++ b/core/res/res/drawable/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable/btn_dropdown_down.9.png b/core/res/res/drawable/btn_dropdown_down.9.png
new file mode 100644
index 0000000..9464139
--- /dev/null
+++ b/core/res/res/drawable/btn_dropdown_down.9.png
Binary files differ
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..16edcec
--- /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..405a5e2
--- /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..467ce8b
--- /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_flicker_minus.png b/core/res/res/drawable/btn_flicker_minus.png
new file mode 100755
index 0000000..9001654
--- /dev/null
+++ b/core/res/res/drawable/btn_flicker_minus.png
Binary files differ
diff --git a/core/res/res/drawable/btn_flicker_plus.png b/core/res/res/drawable/btn_flicker_plus.png
new file mode 100755
index 0000000..2d34a46
--- /dev/null
+++ b/core/res/res/drawable/btn_flicker_plus.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..de865b6
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_off_disable.png b/core/res/res/drawable/btn_radio_off_disable.png
new file mode 100755
index 0000000..7fa1609
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_off_disable_focused.png b/core/res/res/drawable/btn_radio_off_disable_focused.png
new file mode 100755
index 0000000..10eb731
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_off_longpress.png b/core/res/res/drawable/btn_radio_off_longpress.png
new file mode 100644
index 0000000..2ca7dda
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_off_longpress.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..3c92a24
--- /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..4c7f4f2
--- /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..01be1e3
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_on_disable.png b/core/res/res/drawable/btn_radio_on_disable.png
new file mode 100755
index 0000000..f442674
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on_disable.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_on_disable_focused.png b/core/res/res/drawable/btn_radio_on_disable_focused.png
new file mode 100755
index 0000000..b372b31
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on_disable_focused.png
Binary files differ
diff --git a/core/res/res/drawable/btn_radio_on_longpress.png b/core/res/res/drawable/btn_radio_on_longpress.png
new file mode 100644
index 0000000..e6d9f73
--- /dev/null
+++ b/core/res/res/drawable/btn_radio_on_longpress.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..03779e4
--- /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..ed866bd
--- /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..bb15404
--- /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..45482b9
--- /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..3fbe92a
--- /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..1c329a1
--- /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..2a965a7
--- /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..2c1e207
--- /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_off.png b/core/res/res/drawable/btn_star_big_off.png
new file mode 100755
index 0000000..21ba557
--- /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_longpress.png b/core/res/res/drawable/btn_star_big_off_longpress.png
new file mode 100755
index 0000000..8b9815a
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_off_longpress.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..2c704ee
--- /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..101357d
--- /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..9c2f7d2
--- /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_longpress.png b/core/res/res/drawable/btn_star_big_on_longpress.png
new file mode 100755
index 0000000..fbedb53
--- /dev/null
+++ b/core/res/res/drawable/btn_star_big_on_longpress.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..8ac4bab
--- /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..d453eab
--- /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_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_white_longpress.9.png b/core/res/res/drawable/btn_white_longpress.9.png
new file mode 100644
index 0000000..546e5de
--- /dev/null
+++ b/core/res/res/drawable/btn_white_longpress.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.xml b/core/res/res/drawable/checkbox.xml
new file mode 100644
index 0000000..7a22185
--- /dev/null
+++ b/core/res/res/drawable/checkbox.xml
@@ -0,0 +1,29 @@
+<?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">
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/checkbox_on_background_focus_yellow" />
+    <item android:state_checked="false" android:state_focused="true"
+          android:drawable="@drawable/checkbox_off_background_focus_yellow" />
+    <item android:state_checked="false" android:drawable="@drawable/checkbox_off_background" />
+    <item android:state_checked="true" android:drawable="@drawable/checkbox_on_background" />
+</selector>
+
diff --git a/core/res/res/drawable/checkbox_background.xml b/core/res/res/drawable/checkbox_background.xml
new file mode 100644
index 0000000..68bb178
--- /dev/null
+++ b/core/res/res/drawable/checkbox_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/checkbox_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:drawable="@drawable/checkbox_label_background" />
+</selector>
diff --git a/core/res/res/drawable/checkbox_label_background.9.png b/core/res/res/drawable/checkbox_label_background.9.png
new file mode 100644
index 0000000..e6af4b07
--- /dev/null
+++ b/core/res/res/drawable/checkbox_label_background.9.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_off_background_focus_yellow.png b/core/res/res/drawable/checkbox_off_background_focus_yellow.png
new file mode 100644
index 0000000..ffde6f8
--- /dev/null
+++ b/core/res/res/drawable/checkbox_off_background_focus_yellow.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/checkbox_on_background_focus_yellow.png b/core/res/res/drawable/checkbox_on_background_focus_yellow.png
new file mode 100644
index 0000000..3018009
--- /dev/null
+++ b/core/res/res/drawable/checkbox_on_background_focus_yellow.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/contextual_menu_bottom_bright.9.png b/core/res/res/drawable/contextual_menu_bottom_bright.9.png
new file mode 100644
index 0000000..2b222fa
--- /dev/null
+++ b/core/res/res/drawable/contextual_menu_bottom_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/contextual_menu_top_dark.9.png b/core/res/res/drawable/contextual_menu_top_dark.9.png
new file mode 100644
index 0000000..69a59ea
--- /dev/null
+++ b/core/res/res/drawable/contextual_menu_top_dark.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..a1ba2d3
--- /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..7b32381
--- /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..20bc4dc
--- /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_arrowdown.png b/core/res/res/drawable/editbox_dropdown_arrowdown.png
new file mode 100644
index 0000000..82dc409
--- /dev/null
+++ b/core/res/res/drawable/editbox_dropdown_arrowdown.png
Binary files differ
diff --git a/core/res/res/drawable/editbox_dropdown_arrowup.png b/core/res/res/drawable/editbox_dropdown_arrowup.png
new file mode 100644
index 0000000..a84512a
--- /dev/null
+++ b/core/res/res/drawable/editbox_dropdown_arrowup.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/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/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/grey_list_separator.9.png b/core/res/res/drawable/grey_list_separator.9.png
new file mode 100644
index 0000000..8a1a336
--- /dev/null
+++ b/core/res/res/drawable/grey_list_separator.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_disabled_pressed.9.png b/core/res/res/drawable/highlight_disabled_pressed.9.png
new file mode 100644
index 0000000..ee35d39
--- /dev/null
+++ b/core/res/res/drawable/highlight_disabled_pressed.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_pressed_dimmed.9.png b/core/res/res/drawable/highlight_pressed_dimmed.9.png
new file mode 100644
index 0000000..dad714b
--- /dev/null
+++ b/core/res/res/drawable/highlight_pressed_dimmed.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_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_check_mark_dark.png b/core/res/res/drawable/ic_check_mark_dark.png
new file mode 100644
index 0000000..7de2553
--- /dev/null
+++ b/core/res/res/drawable/ic_check_mark_dark.png
Binary files differ
diff --git a/core/res/res/drawable/ic_check_mark_light.png b/core/res/res/drawable/ic_check_mark_light.png
new file mode 100644
index 0000000..137696e
--- /dev/null
+++ b/core/res/res/drawable/ic_check_mark_light.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_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..14c002e
--- /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..c89291a
--- /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..4748b9e
--- /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_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_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_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_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_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_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_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_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_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_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_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_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_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_power.png b/core/res/res/drawable/ic_power.png
new file mode 100755
index 0000000..cfdf422
--- /dev/null
+++ b/core/res/res/drawable/ic_power.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_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/indicator_check_mark_dark.xml b/core/res/res/drawable/indicator_check_mark_dark.xml
new file mode 100644
index 0000000..9713cc4
--- /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/ic_check_mark_dark" />
+
+    <item android:state_checked="false"
+        android:drawable="@color/transparent" />
+        
+     <item
+        android:drawable="@drawable/ic_check_mark_dark" />
+        
+</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..e0129e9
--- /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/ic_check_mark_light" />
+
+    <item android:state_checked="false"
+        android:drawable="@color/transparent" />
+        
+     <item
+        android:drawable="@drawable/ic_check_mark_light" />
+        
+</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/indicator_show_current_selected_dark.png b/core/res/res/drawable/indicator_show_current_selected_dark.png
new file mode 100755
index 0000000..9c77920
--- /dev/null
+++ b/core/res/res/drawable/indicator_show_current_selected_dark.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/lock_asset.png b/core/res/res/drawable/lock_asset.png
new file mode 100644
index 0000000..5d52537
--- /dev/null
+++ b/core/res/res/drawable/lock_asset.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..e7266b2
--- /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_disabled.9.png b/core/res/res/drawable/panel_picture_frame_bg_disabled.9.png
new file mode 100644
index 0000000..786b361
--- /dev/null
+++ b/core/res/res/drawable/panel_picture_frame_bg_disabled.9.png
Binary files differ
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_divider.png b/core/res/res/drawable/pickerbox_divider.png
new file mode 100644
index 0000000..2e82f66
--- /dev/null
+++ b/core/res/res/drawable/pickerbox_divider.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_divider_horizontal_dark.9.png b/core/res/res/drawable/popup_divider_horizontal_dark.9.png
new file mode 100644
index 0000000..b69619b
--- /dev/null
+++ b/core/res/res/drawable/popup_divider_horizontal_dark.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_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..a539ec7
--- /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..1e3f547b
--- /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..fb86cf1
--- /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..da54fe7
--- /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..879a762
--- /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_frame.9.png b/core/res/res/drawable/progress_frame.9.png
new file mode 100644
index 0000000..70bd484
--- /dev/null
+++ b/core/res/res/drawable/progress_frame.9.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_inner.9.png b/core/res/res/drawable/progress_inner.9.png
new file mode 100644
index 0000000..1db9b65
--- /dev/null
+++ b/core/res/res/drawable/progress_inner.9.png
Binary files differ
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_particle_small.png b/core/res/res/drawable/progress_particle_small.png
new file mode 100644
index 0000000..397d8ac
--- /dev/null
+++ b/core/res/res/drawable/progress_particle_small.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.xml b/core/res/res/drawable/radiobutton.xml
new file mode 100644
index 0000000..a72c825
--- /dev/null
+++ b/core/res/res/drawable/radiobutton.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/radiobutton.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_checked="false" android:state_focused="true"
+          android:drawable="@drawable/radiobutton_off_background_focus_yellow" />
+    <item android:state_checked="true" android:state_focused="true"
+          android:drawable="@drawable/radiobutton_on_background_focus_yellow" />
+    <item android:state_checked="false" android:drawable="@drawable/radiobutton_off_background" />
+    <item android:state_checked="true" android:drawable="@drawable/radiobutton_on_background" />
+</selector>
diff --git a/core/res/res/drawable/radiobutton_background.xml b/core/res/res/drawable/radiobutton_background.xml
new file mode 100644
index 0000000..948a141
--- /dev/null
+++ b/core/res/res/drawable/radiobutton_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/drawable/radiobutton_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:drawable="@drawable/radiobutton_label_background" />
+</selector>
diff --git a/core/res/res/drawable/radiobutton_label_background.9.png b/core/res/res/drawable/radiobutton_label_background.9.png
new file mode 100644
index 0000000..e6af4b07
--- /dev/null
+++ b/core/res/res/drawable/radiobutton_label_background.9.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_off_background_focus_yellow.png b/core/res/res/drawable/radiobutton_off_background_focus_yellow.png
new file mode 100644
index 0000000..1a092e3
--- /dev/null
+++ b/core/res/res/drawable/radiobutton_off_background_focus_yellow.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/radiobutton_on_background_focus_yellow.png b/core/res/res/drawable/radiobutton_on_background_focus_yellow.png
new file mode 100644
index 0000000..aa59771
--- /dev/null
+++ b/core/res/res/drawable/radiobutton_on_background_focus_yellow.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..875b0ff
--- /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/btn_rating_star_off_normal" />
+    <item android:id="@+android:id/secondaryProgress" android:drawable="@android:drawable/btn_rating_star_off_normal" />
+    <item android:id="@+android:id/progress" android:drawable="@android:drawable/btn_rating_star_on_normal" />
+</layer-list>
+
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/screen_title_background.9.png b/core/res/res/drawable/screen_title_background.9.png
new file mode 100644
index 0000000..fed37b8
--- /dev/null
+++ b/core/res/res/drawable/screen_title_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/scroll_thumb_horz.9.png b/core/res/res/drawable/scroll_thumb_horz.9.png
new file mode 100644
index 0000000..5c7db1a
--- /dev/null
+++ b/core/res/res/drawable/scroll_thumb_horz.9.png
Binary files differ
diff --git a/core/res/res/drawable/scroll_thumb_vert.9.png b/core/res/res/drawable/scroll_thumb_vert.9.png
new file mode 100644
index 0000000..ef660c3
--- /dev/null
+++ b/core/res/res/drawable/scroll_thumb_vert.9.png
Binary files differ
diff --git a/core/res/res/drawable/scrollbar.9.png b/core/res/res/drawable/scrollbar.9.png
new file mode 100644
index 0000000..6e2268b
--- /dev/null
+++ b/core/res/res/drawable/scrollbar.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..d96cb3f
--- /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..f333733
--- /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..ff08295
--- /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.png b/core/res/res/drawable/seek_thumb.png
new file mode 100644
index 0000000..dbaae91
--- /dev/null
+++ b/core/res/res/drawable/seek_thumb.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..0a5e4c8
--- /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..240a982
--- /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/spinnerbox_background_focus_yellow.9.png b/core/res/res/drawable/spinnerbox_background_focus_yellow.9.png
new file mode 100644
index 0000000..f1b8692
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_background_focus_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_background_normal.9.png b/core/res/res/drawable/spinnerbox_background_normal.9.png
new file mode 100644
index 0000000..c64de3c
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_background_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable/spinnerbox_background_pressed_yellow.9.png b/core/res/res/drawable/spinnerbox_background_pressed_yellow.9.png
new file mode 100644
index 0000000..984d2b8
--- /dev/null
+++ b/core/res/res/drawable/spinnerbox_background_pressed_yellow.9.png
Binary files differ
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/starting_frame.9.png b/core/res/res/drawable/starting_frame.9.png
new file mode 100644
index 0000000..4b2b78c
--- /dev/null
+++ b/core/res/res/drawable/starting_frame.9.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_browser.png b/core/res/res/drawable/stat_notify_browser.png
new file mode 100644
index 0000000..f08534b
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_browser.png
Binary files differ
diff --git a/core/res/res/drawable/stat_notify_calibrate_compass.png b/core/res/res/drawable/stat_notify_calibrate_compass.png
new file mode 100755
index 0000000..28bd386
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_calibrate_compass.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_notify_xmpp.png b/core/res/res/drawable/stat_notify_xmpp.png
new file mode 100644
index 0000000..dc79203
--- /dev/null
+++ b/core/res/res/drawable/stat_notify_xmpp.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_bar_item_clear_background.9.png b/core/res/res/drawable/status_bar_item_clear_background.9.png
new file mode 100644
index 0000000..e3036e6
--- /dev/null
+++ b/core/res/res/drawable/status_bar_item_clear_background.9.png
Binary files differ
diff --git a/core/res/res/drawable/status_bar_shadow_bar.9.png b/core/res/res/drawable/status_bar_shadow_bar.9.png
new file mode 100644
index 0000000..9c4463c
--- /dev/null
+++ b/core/res/res/drawable/status_bar_shadow_bar.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..6af7329
--- /dev/null
+++ b/core/res/res/drawable/statusbar_background.png
Binary files differ
diff --git a/core/res/res/drawable/sub_menu_bottom_bright.9.png b/core/res/res/drawable/sub_menu_bottom_bright.9.png
new file mode 100644
index 0000000..ce93435
--- /dev/null
+++ b/core/res/res/drawable/sub_menu_bottom_bright.9.png
Binary files differ
diff --git a/core/res/res/drawable/sub_menu_top_dark.9.png b/core/res/res/drawable/sub_menu_top_dark.9.png
new file mode 100644
index 0000000..9e82fe9
--- /dev/null
+++ b/core/res/res/drawable/sub_menu_top_dark.9.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_back.png b/core/res/res/drawable/submenu_arrow_back.png
new file mode 100644
index 0000000..733aa13
--- /dev/null
+++ b/core/res/res/drawable/submenu_arrow_back.png
Binary files differ
diff --git a/core/res/res/drawable/submenu_arrow_focus.png b/core/res/res/drawable/submenu_arrow_focus.png
new file mode 100644
index 0000000..2ed1599
--- /dev/null
+++ b/core/res/res/drawable/submenu_arrow_focus.png
Binary files differ
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_call.png b/core/res/res/drawable/sym_action_call.png
new file mode 100644
index 0000000..bcd9010
--- /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..625e0e8
--- /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..5f79e92
--- /dev/null
+++ b/core/res/res/drawable/sym_action_email.png
Binary files differ
diff --git a/core/res/res/drawable/sym_action_map.png b/core/res/res/drawable/sym_action_map.png
new file mode 100644
index 0000000..b45b7a8
--- /dev/null
+++ b/core/res/res/drawable/sym_action_map.png
Binary files differ
diff --git a/core/res/res/drawable/sym_action_sms.png b/core/res/res/drawable/sym_action_sms.png
new file mode 100644
index 0000000..50ce0ea
--- /dev/null
+++ b/core/res/res/drawable/sym_action_sms.png
Binary files differ
diff --git a/core/res/res/drawable/sym_battery_white.png b/core/res/res/drawable/sym_battery_white.png
new file mode 100644
index 0000000..d1849fc
--- /dev/null
+++ b/core/res/res/drawable/sym_battery_white.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/sym_default_number.png b/core/res/res/drawable/sym_default_number.png
new file mode 100644
index 0000000..b1ed071
--- /dev/null
+++ b/core/res/res/drawable/sym_default_number.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_bottom_shadow.9.png b/core/res/res/drawable/tab_bottom_shadow.9.png
new file mode 100755
index 0000000..3ac4f53
--- /dev/null
+++ b/core/res/res/drawable/tab_bottom_shadow.9.png
Binary files differ
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_selected_highlight.9.png b/core/res/res/drawable/tab_selected_highlight.9.png
new file mode 100644
index 0000000..61568a3
--- /dev/null
+++ b/core/res/res/drawable/tab_selected_highlight.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/tab_unselected_longpress.9.png b/core/res/res/drawable/tab_unselected_longpress.9.png
new file mode 100644
index 0000000..65556c4
--- /dev/null
+++ b/core/res/res/drawable/tab_unselected_longpress.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_unselected_pressed.9.png b/core/res/res/drawable/tab_unselected_pressed.9.png
new file mode 100644
index 0000000..b4102eb
--- /dev/null
+++ b/core/res/res/drawable/tab_unselected_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable/tab_unselected_selected.9.png b/core/res/res/drawable/tab_unselected_selected.9.png
new file mode 100644
index 0000000..320f754
--- /dev/null
+++ b/core/res/res/drawable/tab_unselected_selected.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..ab99aeb
--- /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..0070158
--- /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 100755
index 0000000..139d606
--- /dev/null
+++ b/core/res/res/drawable/textfield_disabled_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_expanded_bottom_selected.9.png b/core/res/res/drawable/textfield_expanded_bottom_selected.9.png
new file mode 100755
index 0000000..935acaf
--- /dev/null
+++ b/core/res/res/drawable/textfield_expanded_bottom_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_expanded_bottom_unselected.9.png b/core/res/res/drawable/textfield_expanded_bottom_unselected.9.png
new file mode 100755
index 0000000..1e5f28d
--- /dev/null
+++ b/core/res/res/drawable/textfield_expanded_bottom_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_expanded_center_selected.9.png b/core/res/res/drawable/textfield_expanded_center_selected.9.png
new file mode 100755
index 0000000..b60a0ad
--- /dev/null
+++ b/core/res/res/drawable/textfield_expanded_center_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_expanded_center_unselected.9.png b/core/res/res/drawable/textfield_expanded_center_unselected.9.png
new file mode 100755
index 0000000..f38c58f
--- /dev/null
+++ b/core/res/res/drawable/textfield_expanded_center_unselected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_expanded_top_selected.9.png b/core/res/res/drawable/textfield_expanded_top_selected.9.png
new file mode 100755
index 0000000..926d09f
--- /dev/null
+++ b/core/res/res/drawable/textfield_expanded_top_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/textfield_expanded_top_unselected.9.png b/core/res/res/drawable/textfield_expanded_top_unselected.9.png
new file mode 100755
index 0000000..ab1b1ad
--- /dev/null
+++ b/core/res/res/drawable/textfield_expanded_top_unselected.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..7b1350f
--- /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..7286ba5
--- /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..af72d22
--- /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..2d80424
--- /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..c427fc3
--- /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..ac6ac53
--- /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..f710b57
--- /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..97da87a
--- /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..eb101c5
--- /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..c83b1eb
--- /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..e152848
--- /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..1814bb4
--- /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..9ad5b85
--- /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..35fc221
--- /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..c910777
--- /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..549a7e5
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable/title_bar.9.png b/core/res/res/drawable/title_bar.9.png
new file mode 100644
index 0000000..8d18339
--- /dev/null
+++ b/core/res/res/drawable/title_bar.9.png
Binary files differ
diff --git a/core/res/res/drawable/title_bar_shadow.png b/core/res/res/drawable/title_bar_shadow.png
new file mode 100644
index 0000000..a717814
--- /dev/null
+++ b/core/res/res/drawable/title_bar_shadow.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_in.png b/core/res/res/drawable/zoom_in.png
new file mode 100644
index 0000000..d151046
--- /dev/null
+++ b/core/res/res/drawable/zoom_in.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_in_pressed.png b/core/res/res/drawable/zoom_in_pressed.png
new file mode 100644
index 0000000..963bf73
--- /dev/null
+++ b/core/res/res/drawable/zoom_in_pressed.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_indicator.9.png b/core/res/res/drawable/zoom_indicator.9.png
new file mode 100644
index 0000000..c799193
--- /dev/null
+++ b/core/res/res/drawable/zoom_indicator.9.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_indicator_selected.png b/core/res/res/drawable/zoom_indicator_selected.png
new file mode 100644
index 0000000..20a72c1
--- /dev/null
+++ b/core/res/res/drawable/zoom_indicator_selected.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_out.png b/core/res/res/drawable/zoom_out.png
new file mode 100644
index 0000000..2efbc47
--- /dev/null
+++ b/core/res/res/drawable/zoom_out.png
Binary files differ
diff --git a/core/res/res/drawable/zoom_out_pressed.png b/core/res/res/drawable/zoom_out_pressed.png
new file mode 100644
index 0000000..39e145c
--- /dev/null
+++ b/core/res/res/drawable/zoom_out_pressed.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_track.png b/core/res/res/drawable/zoom_track.png
new file mode 100644
index 0000000..71e71a9
--- /dev/null
+++ b/core/res/res/drawable/zoom_track.png
Binary files differ
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..761f767
--- /dev/null
+++ b/core/res/res/layout-land/icon_menu_layout.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.
+-->
+
+<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:maxRows="1"
+    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..05ffe106
--- /dev/null
+++ b/core/res/res/layout-port/icon_menu_layout.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.
+-->
+
+<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:maxRows="2"
+    android:maxItemsPerRow="3" />
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/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
new file mode 100644
index 0000000..e3de682
--- /dev/null
+++ b/core/res/res/layout/alert_dialog.xml
@@ -0,0 +1,145 @@
+<?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" />
+            <TextView android:id="@+id/alertTitle" 
+                style="?android:attr/textAppearanceMedium"
+                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/always_use_checkbox.xml b/core/res/res/layout/always_use_checkbox.xml
new file mode 100644
index 0000000..90c9a44
--- /dev/null
+++ b/core/res/res/layout/always_use_checkbox.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.
+-->
+
+<!-- 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">
+    
+    <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/useDefaultText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:paddingLeft="6dip"
+        android:layout_toRightOf="@id/alwaysUse"
+        android:text="@string/alwaysUse" />
+
+    <TextView 
+        android:id="@+id/clearDefaultHint"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:paddingLeft="6dip"
+        android:visibility="gone"
+        android:layout_below="@id/useDefaultText"
+        android:layout_toRightOf="@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..a398bd0
--- /dev/null
+++ b/core/res/res/layout/date_picker.xml
@@ -0,0 +1,62 @@
+<?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_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..879f3398
--- /dev/null
+++ b/core/res/res/layout/date_picker_dialog.xml
@@ -0,0 +1,27 @@
+<?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="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="5dip">
+    <DatePicker android:id="@+id/datePicker"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"/>
+</FrameLayout>
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..cd4ea12
--- /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="320dip"
+	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..4383127
--- /dev/null
+++ b/core/res/res/layout/global_actions_item.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingTop="10dip"
+    android:paddingBottom="10dip"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    >
+
+    <ImageView android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginRight="6dip"
+        android:layout_gravity="center_vertical"
+            />
+
+    <LinearLayout android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical">
+
+        <TextView android:id="@+id/message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            />
+
+        <TextView android:id="@+id/status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmallInverse" 
+            />
+    </LinearLayout>
+
+</LinearLayout>
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..7409621
--- /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/progress"
+            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..06f6098
--- /dev/null
+++ b/core/res/res/layout/icon_menu_item_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.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:gravity="bottom|center_horizontal"
+    android:singleLine="true"
+    android:ellipsize="end" />
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..9f306cc
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
@@ -0,0 +1,115 @@
+<?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:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#FF000000"
+        >
+
+    <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:singleLine="true"
+        />
+
+    <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:password="true"
+        android:hint="@android:string/lockscreen_glogin_password_hint"
+        android:singleLine="true"
+        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"
+        />
+
+    <!-- emergency call button at bottom center -->
+    <Button
+        android:id="@+id/emergencyCall"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="8dip"
+        android:layout_centerHorizontal="true"
+        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"/-->
+
+
+</RelativeLayout>
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..f5c850a
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_lock.xml
@@ -0,0 +1,218 @@
+<?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:background="#FF000000"
+    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: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_bright"/>
+
+        <!-- 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_bright"
+                />
+
+        <!-- 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_bright"
+                />
+
+        <!-- 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_bright"/>
+
+        <!-- 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: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:layout_marginTop="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..fd70f50
--- /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="#FF000000"
+        >
+
+    <!-- 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:password="true"
+            />
+
+        <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..566da21
--- /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="#FF000000"
+        >
+
+    <!-- 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:password="true"
+            />
+
+        <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..f890643
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.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.
+*/
+-->
+
+<!-- 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:background="#FF000000"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+        >
+
+    <!-- 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"
+                    />
+                <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..c888411
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.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.
+*/
+-->
+
+<!-- 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:background="#FF000000"
+    android:layout_width="wrap_content"
+    android:layout_height="fill_parent"
+        >
+
+    <!-- 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: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:textSize="14sp"
+                />
+        </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..3af1261
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_layout.xml
@@ -0,0 +1,55 @@
+<?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, checkbox, and/or radio button 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" />
+
+        <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>
+
+</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..422733a
--- /dev/null
+++ b/core/res/res/layout/number_picker.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.
+*/
+-->
+
+<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: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..46f4845
--- /dev/null
+++ b/core/res/res/layout/number_picker_edit.xml
@@ -0,0 +1,28 @@
+<?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: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..3f589a4
--- /dev/null
+++ b/core/res/res/layout/preference.xml
@@ -0,0 +1,58 @@
+<?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">
+    
+    <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" />
+            
+        <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..26a1046
--- /dev/null
+++ b/core/res/res/layout/preference_child.xml
@@ -0,0 +1,51 @@
+<?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="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical">
+    
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="20sp"
+        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" />
+            
+        <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:maxLines="2"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            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" />
+
+</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..0481857
--- /dev/null
+++ b/core/res/res/layout/preference_information.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 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">
+    
+    <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..eda1c73
--- /dev/null
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -0,0 +1,41 @@
+<?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:paddingLeft="14dip"
+    android:paddingRight="15dip">
+
+    <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_vertical"
+        android:scaleType="fitCenter" />
+
+    <TextView android:id="@android:id/text1"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:minHeight="?android:attr/listPreferredItemHeight"
+        android:textAppearance="?android:attr/textAppearanceLargeInverse"
+        android:gravity="center_vertical"
+        android:paddingLeft="14dip" />
+</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..f8c96cc
--- /dev/null
+++ b/core/res/res/layout/search_bar.xml
@@ -0,0 +1,101 @@
+<?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="14dip"
+    android:orientation="vertical" 
+    android:focusable="true"
+    android:descendantFocusability="afterDescendants">
+
+    <!-- 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: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" >
+
+            <EditText
+                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:singleLine="true" />
+                
+            <Button
+                android:id="@+id/search_go_btn"
+                android:text="@string/search_go"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="6dip" />
+        </LinearLayout>
+        
+    </LinearLayout>
+
+    <!-- The margintop of -21 adjusts the listview to abut the bottom of the edittext. -->
+    <ListView 
+        android:id="@+id/search_suggest_list"
+        android:layout_marginTop="-21dip"
+        android:layout_width="200dip"
+        android:layout_height="fill_parent"
+        android:background="@android:drawable/spinner_dropdown_background_down"
+        android:divider="@android:drawable/divider_horizontal_bright" 
+        android:cacheColorHint="#FFFFFFFF" />
+
+</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..3b607ca
--- /dev/null
+++ b/core/res/res/layout/select_dialog_item.xml
@@ -0,0 +1,35 @@
+<?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/textAppearanceLargeInverse"
+    android:gravity="center_vertical"
+    android:paddingLeft="14dip"
+    android:paddingRight="15dip"
+/>
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..eae4800
--- /dev/null
+++ b/core/res/res/layout/select_dialog_multichoice.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_check"
+/>
+
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..98f5dbd
--- /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"
+/>
+
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..8994e8c
--- /dev/null
+++ b/core/res/res/layout/simple_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/textAppearanceLargeInverse"
+    android:singleLine="true"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight" />
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..0881b9c
--- /dev/null
+++ b/core/res/res/layout/simple_spinner_dropdown_item.xml
@@ -0,0 +1,25 @@
+<?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" />
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..1c51a48
--- /dev/null
+++ b/core/res/res/layout/simple_spinner_item.xml
@@ -0,0 +1,25 @@
+<?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" />
diff --git a/core/res/res/layout/status_bar.xml b/core/res/res/layout/status_bar.xml
new file mode 100644
index 0000000..dfdaf66
--- /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="25dp"
+        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..fbe4c32
--- /dev/null
+++ b/core/res/res/layout/status_bar_latest_event_content.xml
@@ -0,0 +1,53 @@
+<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: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: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..bdfe490
--- /dev/null
+++ b/core/res/res/layout/time_picker.xml
@@ -0,0 +1,59 @@
+<?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_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"
+        />
+</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..6dc1bf6
--- /dev/null
+++ b/core/res/res/layout/time_picker_dialog.xml
@@ -0,0 +1,27 @@
+<?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="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="5dip">
+    <TimePicker android:id="@+id/timePicker"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"/>
+</FrameLayout>
diff --git a/core/res/res/layout/time_picker_text.xml b/core/res/res/layout/time_picker_text.xml
new file mode 100644
index 0000000..bad980b
--- /dev/null
+++ b/core/res/res/layout/time_picker_text.xml
@@ -0,0 +1,27 @@
+<?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 of time picker-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textAppearance="?attr/textAppearanceLargeInverse"
+    android:gravity="center"
+    />
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..2d1bd5c
--- /dev/null
+++ b/core/res/res/layout/volume_adjust.xml
@@ -0,0 +1,62 @@
+<?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>
+
+    <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..ec37417
--- /dev/null
+++ b/core/res/res/layout/zoom_controls.xml
@@ -0,0 +1,37 @@
+<?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="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@android:drawable/zoom_plate"
+    android:gravity="bottom"
+    android:paddingLeft="15dip"
+    android:paddingRight="15dip">
+    <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"
+        />
+  </LinearLayout>
diff --git a/core/res/res/layout/zoom_magnify.xml b/core/res/res/layout/zoom_magnify.xml
new file mode 100644
index 0000000..b424837
--- /dev/null
+++ b/core/res/res/layout/zoom_magnify.xml
@@ -0,0 +1,38 @@
+<?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:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+    <ZoomControls android:id="@+id/zoomControls"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+    <ImageView android:id="@+id/zoomMagnify"
+        android:focusable="true"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:paddingRight="2dip"
+        android:src="@drawable/btn_zoom_page"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+   </RelativeLayout>
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/values-cs-rCZ/strings.xml b/core/res/res/values-cs-rCZ/strings.xml
new file mode 100644
index 0000000..a6d2c6b
--- /dev/null
+++ b/core/res/res/values-cs-rCZ/strings.xml
@@ -0,0 +1,274 @@
+<?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-array name="day_labels">
+    <item>Ne</item>
+    <item>Po</item>
+    <item>Út</item>
+    <item>St</item>
+    <item>Čt</item>
+    <item>Pá</item>
+    <item>So</item>
+  </string-array>
+  <string-array name="emailAddressTypes">
+    <item>Výchozí</item>
+    <item>Zaměstnání</item>
+    <item>Primární</item>
+    <item>Vlastní\u2026</item>
+  </string-array>
+  <string-array name="phoneTypes">
+    <item>Výchozí</item>
+    <item>Mobil</item>
+    <item>Zaměstnání</item>
+    <item>Fax do zaměstnání</item>
+    <item>Fax domů</item>
+    <item>Operátor</item>
+    <item>Vlastní\u2026</item>
+  </string-array>
+  <string-array name="postalAddressTypes">
+    <item>Poštovní</item>
+    <item>Výchozí</item>
+    <item>Zaměstnání</item>
+    <item>Vlastní\u2026</item>
+  </string-array>
+  <string name="CLIRDefaultOffNextCallOff">Výchozí nastavení omezení ID - bez omezení. Další hovor: bez omezení</string>
+  <string name="CLIRDefaultOffNextCallOn">Výchozí nastavení omezení ID - bez omezení. Další hovor: omezení</string>
+  <string name="CLIRDefaultOnNextCallOff">Výchozí nastavení omezení ID - omezení. Další hovor: bez omezení</string>
+  <string name="CLIRDefaultOnNextCallOn">Výchozí nastavení omezení ID - omezení. Další hovor: omezení</string>
+  <string name="CLIRPermanent">Omezení ID v trvalém režimu.</string>
+  <string name="activate_keyguard">Aktivovat zámek kláves</string>
+  <string name="ago">před</string>
+  <string name="am">dop.</string>
+  <string name="before">Před</string>
+  <string name="browserSavedFormData">Uložená data formuláře</string>
+  <string name="cfReasonBusy">Přesměrování hovorů - je-li obsazeno</string>
+  <string name="cfReasonNR">Přesměrování hovorů - je-li nedostupný</string>
+  <string name="cfReasonNRy">Přesměrování hovorů - neodpovídá-li</string>
+  <string name="cfReasonUnconditional">Přesměrování hovorů - bezpodmínečné</string>
+  <string name="cfTemplateForwarded">{0}: {1}</string>
+  <string name="cfTemplateForwardedTime">{0}: {1} po {2} sekundách</string>
+  <string name="cfTemplateNotForwarded">{0}: Nepřesměrováno</string>
+  <string name="cfTemplateRegistered">{0}: Nepřesměrováno ({1})</string>
+  <string name="cfTemplateRegisteredTime">{0}: Nepřesměrováno ({1} po {2} sekundách)</string>
+  <string name="contentServiceSync">Synchronizace</string>
+  <string name="contentServiceSyncNotificationDesc">Synchronizace</string>
+  <string name="contentServiceSyncNotificationTitle">Synchronizace</string>
+  <string name="contentServiceXmppAvailable">XMPP aktivní</string>
+  <string name="daily">Denně</string>
+  <string name="day">den</string>
+  <string name="days">dnů</string>
+  <string name="defaultMsisdnAlphaTag">Msisdn1</string>
+  <string name="defaultVoiceMailAlphaTag">Hlasová schránka</string>
+  <string name="ellipsis">\u2026</string>
+  <string name="emptyPhoneNumber">(žádné telefonní číslo)</string>
+  <string name="every_weekday">"Každý den v týdnu (Po\u2013Pá)"</string>
+  <string name="factorytest_failed">Výrobní test skončil chybou</string>
+  <string name="factorytest_no_action">Nebyla nalezena žádná sada, která zajišťuje
+        akci FACTORY_TEST.</string>
+  <string name="factorytest_not_system">Akce FACTORY_TEST
+        je podporována pouze pro sady instalované v adresáři /system/app.</string>
+  <string name="factorytest_reboot">Restartovat</string>
+  <string name="friday">Pátek</string>
+  <string name="hour">hodinu</string>
+  <string name="hours">hodin</string>
+  <string name="httpError">Neznámá chyba</string>
+  <string name="httpErrorAuth">Ověřování se nezdařilo</string>
+  <string name="httpErrorBadUrl">Nepodařilo se analyzovat URL</string>
+  <string name="httpErrorConnect">Připojení k serveru se nezdařilo</string>
+  <string name="httpErrorFailedSslHandshake">Navázání spojení typu SSL handshake se nezdařilo</string>
+  <string name="httpErrorIO">Čtení nebo zápis na server se nezdařil</string>
+  <string name="httpErrorLookup">Neznámý hostitel</string>
+  <string name="httpErrorOk">OK</string>
+  <string name="httpErrorProxyAuth">Ověření serverem proxy se nezdařilo</string>
+  <string name="httpErrorRedirectLoop">Příliš mnoho přesměrování serverů</string>
+  <string name="httpErrorTimeout">Časový limit připojení k serveru vypršel</string>
+  <string name="httpErrorUnsupportedAuthScheme">Nepodporované schéma ověření. Ověření se nezdařilo.</string>
+  <string name="httpErrorUnsupportedScheme">Nepodporovaný protokol</string>
+  <!-- generic file error --><string name="httpErrorFile">File error</string>
+  <!-- file not found error --><string name="httpErrorFileNotFound">File not found</string>
+  <string name="in">v</string>
+  <string name="keyguard_label_text">Telefon odemknete stisknutím tlačítka nabídky a poté 0.</string>
+  <string name="keyguard_password_emergency_instructions">Chcete-li provést tísňové volání, stiskněte tlačítko Odeslat.</string>
+  <string name="keyguard_password_instructions">Zadejte heslo nebo vytočte číslo tísňového volání.</string>
+  <string name="minute">minutu</string>
+  <string name="minutes">minut</string>
+  <string name="mmiComplete">MMI dokončeno</string>
+  <string name="mmiError">Chyba sítě nebo neplatný kód MMI.</string>
+  <string name="monday">Pondělí</string>
+  <string name="monthly">Měsíčně</string>
+  <string name="more_item_label">Další</string>
+  <string name="passwordIncorrect">Nesprávné heslo.</string>
+  <string name="permdesc_accessCoarseLocation">Identifikátory pro technologii využívající polohu vysílačů mobilních sítí (je-li k dispozici) se používají k určení přibližné polohy zařízení. Toto oprávnění vyžaduje oprávnění ACCESS_LOCATION. Škodlivé aplikace toho mohou využívat k určení vaší přibližné polohy.</string>
+  <string name="permdesc_accessFineLocation">Technologii GPS v zařízení lze používat, pokud je k dispozici. Toto oprávnění vyžaduje oprávnění ACCESS_LOCATION. Škodlivé aplikace toho mohou využívat k určení vaší polohy a mohou spotřebovávat zbytečně energii baterie.</string>
+  <string name="permdesc_accessSurfaceFlinger">Umožňuje aplikacím používat
+        funkce nižší úrovně SurfaceFlinger.</string>
+  <string name="permdesc_addSystemService">Umožňuje aplikacím vydávat
+        vlastní systémové služby nižší úrovně. Škodlivé aplikace mohou napadnout
+        systém a vykrást nebo poškodit jeho data.</string>
+  <string name="permdesc_broadcastPackageRemoved">Umožňuje aplikacím
+        vysílat oznámení o odebrání sady aplikací.
+        Škodlivé aplikace toho mohou využít k likvidaci jiné spuštěné
+        aplikace.</string>
+  <string name="permdesc_broadcastSticky">Umožňuje aplikacím odesílat
+        tzv. lepivé (sticky) vysílání, které zůstává i po ukončení vysílání.
+        Škodlivé aplikace mohou zpomalit zařízení nebo narušit jeho stabilitu
+        vynucením využívání příliš velké části paměti.</string>
+  <string name="permdesc_callPhone">Umožňuje aplikacím volat
+        telefonní čísla bez vašeho zásahu. Škodlivé aplikace mohou
+        přinést na váš telefonní účet neočekávané hovory.</string>
+  <string name="permdesc_changeComponentState">Umožňuje změnu aplikace bez ohledu na to, zda
+        je součást další aplikace povolená nebo zakázaná. Škodlivá aplikace toho může využít
+        k zakázání důležitých funkcí zařízení. Je třeba nakládat s oprávněními opatrně, protože
+        se mohou součásti aplikace dostat do stavu nepoužitelnosti, nekonzistence nebo nestability.
+    </string>
+  <string name="permdesc_deletePackages">Umožňuje aplikacím odstranit
+        sady systému Android. Škodlivé aplikace toho mohou využít k odstranění důležitých aplikací.</string>
+  <string name="permdesc_dump">Umožňuje aplikacím načítat
+        vnitřní stav systému. Škodlivé aplikace mohou načítat
+        široký rozsah soukromých a důvěrných informací, jež by
+        obvykle neměly nikdy vyžadovat.</string>
+  <string name="permdesc_fotaUpdate">Umožňuje aplikacím přijímat
+        oznámení o aktualizacích systému čekajících na dokončení a spouštět jejich
+        instalaci. Škodlivé aplikace toho mohou využít k poškození systému
+        neautorizovanými aktualizacemi nebo obecně k zásahům do aktualizačního
+        procesu.</string>
+  <string name="permdesc_getTasks">Umožňuje aplikacím načítat
+        informace o aktuálně a naposledy spuštěných úkolech. Umožňuje
+        škodlivým aplikacím
+        zjišťovat soukromé informace o jiných aplikacích.</string>
+  <string name="permdesc_installPackages">Umožňuje aplikacím instalovat nové nebo aktualizované
+        sady systému Android. Škodlivé aplikace toho mohou využít k přidání nových aplikací s libovolně
+        silnými oprávněními.</string>
+  <string name="permdesc_internalSystemWindow">Umožňuje vytváření
+        oken určených k použití uživatelským rozhraním vnitřního systému
+        . Není určeno k použití normálními aplikacemi.</string>
+  <string name="permdesc_raisedThreadPriority">Umožňuje aplikacím používat
+        vyšších priorit vláken, případně ovlivnit reagování uživatelského rozhraní
+        .</string>
+  <string name="permdesc_readContacts">Umožňuje aplikacím číst všechna
+        data o kontaktech (adresy) uložená v zařízení. Škodlivé aplikace
+        toho mohou využívat k odesílání vašich dat jiným osobám.</string>
+  <string name="permdesc_readFrameBuffer">Umožňuje aplikacím používat
+        čtení obsahu vyrovnávací paměti rámce.</string>
+  <string name="permdesc_receiveBootCompleted">Umožňuje aplikacím
+        spouštět se po dokončení spuštění systému.
+        Tím se může prodlužovat doba spouštění zařízení a
+        aplikace může svým stálým spouštěním zpomalovat celé zařízení.</string>
+  <string name="permdesc_receiveSms">Umožňuje aplikacím přijímat
+      a zpracovávat zprávy SMS. Škodlivé aplikace mohou sledovat
+      vaše zprávy nebo je odstraňovat, aniž by se zobrazily.</string>
+  <string name="permdesc_receiveMms">Umožňuje aplikacím přijímat
+      a zpracovávat zprávy MMS. Škodlivé aplikace mohou sledovat
+      vaše zprávy nebo je odstraňovat, aniž by se zobrazily.</string>
+  <string name="permdesc_receiveWapPush">Umožňuje aplikacím přijímat
+      a zpracovávat zprávy WAP. Škodlivé aplikace mohou sledovat
+      vaše zprávy nebo je odstraňovat, aniž by se zobrazily.</string>
+  <string name="permdesc_runInstrumentation">Umožňuje aplikacím
+        vložit kód vlastního nástroje do libovolné jiné aplikace.
+        Škodlivé aplikace mohou zcela zničit systém. Toto
+        oprávnění je nutné pouze pro vývoj, nikdy pro normální
+        používání zařízení.</string>
+  <string name="permdesc_runSetActivityWatcher">Umožňuje aplikacím
+        sledovat a řídit spouštění činností systému.
+        Škodlivé aplikace mohou zcela zničit systém. Toto
+        oprávnění je nutné pouze pro vývoj, nikdy pro normální
+        používání zařízení.</string>
+  <string name="permdesc_setPreferredApplications">Umožňuje aplikacím
+        upravovat oblíbené aplikace. Škodlivé aplikace tak mohou
+        bez upozornění měnit spouštěné aplikace a klamně využívat
+        stávající aplikace ke shromažďování vašich soukromých dat.</string>
+  <string name="permdesc_signalPersistentProcesses">Umožňuje aplikacím vyžadovat, aby
+        se přiváděný signál odesílal do všech trvalých procesů.</string>
+  <string name="permdesc_systemAlertWindow">Umožňuje aplikacím
+        zobrazovat okna systémových výstrah. Škodlivé aplikace mohou ovládnout
+        celou obrazovku zařízení.</string>
+  <string name="permdesc_writeContacts">Umožňuje aplikacím upravovat
+        data o kontaktech (adresy) uložená v zařízení. Škodlivé
+        aplikace toho mohou využívat k vymazání nebo úpravě dat o kontaktech.</string>
+  <string name="permdesc_writeSettings">Umožňuje aplikacím upravovat
+        data nastavení systému. Škodlivé aplikace mohou narušit systémovou
+        konfiguraci.</string>
+  <string name="permlab_accessCoarseLocation">Používat službu Cell ID</string>
+  <string name="permlab_accessFineLocation">Používat službu GPS</string>
+  <string name="permlab_accessSurfaceFlinger">Používat službu SurfaceFlinger</string>
+  <string name="permlab_addSystemService">Přidat systémovou službu</string>
+  <string name="permlab_broadcastPackageRemoved">Sada vysílání odebrána</string>
+  <string name="permlab_broadcastSticky">Vysílat lepivý obsah (sticky)</string>
+  <string name="permlab_callPhone">Volat telefonní čísla</string>
+  <string name="permlab_changeComponentState">Povolit nebo zakázat součásti aplikací</string>
+  <string name="permlab_deletePackages">Odstranit sady</string>
+  <string name="permlab_dump">Výpis stavu systému</string>
+  <string name="permlab_fotaUpdate">Instalace aktualizace systému</string>
+  <string name="permlab_getTasks">Získat informace o úkolech</string>
+  <string name="permlab_installPackages">Instalovat sady</string>
+  <string name="permlab_internalSystemWindow">Okno vnitřního systému</string>
+  <string name="permlab_raisedThreadPriority">Vyšší priority vláken</string>
+  <string name="permlab_readContacts">Čtení dat o kontaktech</string>
+  <string name="permlab_readFrameBuffer">Čtení vyrovnávací paměti rámce</string>
+  <string name="permlab_receiveBootCompleted">Spustit při spouštění</string>
+  <string name="permlab_receiveSms">Příjem zpráv SMS</string>
+  <string name="permlab_receiveMms">Příjem zpráv MMS</string>
+  <string name="permlab_receiveWapPush">Příjem zpráv WAP</string>
+  <string name="permlab_runInstrumentation">Spustit nástroje</string>
+  <string name="permlab_runSetActivityWatcher">Nastavení sledování činností</string>
+  <string name="permlab_setPreferredApplications">Nastavení upřednostňovaných aplikací</string>
+  <string name="permlab_signalPersistentProcesses">Signálové trvalé procesy</string>
+  <string name="permlab_systemAlertWindow">Okno systémových výstrah</string>
+  <string name="permlab_writeContacts">Zápis dat o kontaktech</string>
+  <string name="permlab_writeSettings">Nastavení systému pro zápis</string>
+  <string name="pm">odp.</string>
+  <string name="power_dialog">Možnosti napájení</string>
+  <string name="power_off">Vypnuto</string>
+  <string name="prepend_shortcut_label">Menu+</string>
+  <string name="primaryEmailAddressLabel">E-mail</string>
+  <string name="saturday">Sobota</string>
+  <string name="screen_progress">Pracuji...</string>
+  <string name="search_go">PŘEJÍT</string>
+  <string name="search_hint_placeholder">Zde zadejte hledaný text</string>
+  <string name="second">sekund</string>
+  <string name="seconds">sekund</string>
+  <string name="selectMenuLabel">Vybrat</string>
+  <string name="serviceClassData">Data</string>
+  <string name="serviceClassDataAsync">Asynchronní</string>
+  <string name="serviceClassDataSync">Synchronizace</string>
+  <string name="serviceClassFAX">FAX</string>
+  <string name="serviceClassPAD">PAD</string>
+  <string name="serviceClassPacket">Pakety</string>
+  <string name="serviceClassSMS">SMS</string>
+  <string name="serviceClassVoice">Hlasový záznam</string>
+  <string name="serviceDisabled">Služba zakázána</string>
+  <string name="serviceEnabled">Služba povolena</string>
+  <string name="serviceEnabledFor">Služba povolena pro:</string>
+  <string name="serviceErased">Odstranění úspěšné</string>
+  <string name="serviceNotProvisioned">Služba není poskytována.</string>
+  <string name="serviceRegistered">Registrace úspěšná</string>
+  <string name="silent_mode">Tichý režim</string>
+  <string name="simAbsentLabel">Chybí karta SIM nebo je nesprávně vložena</string>
+  <string name="simNetworkPersonalizationLabel">Tuto kartu SIM nelze v zařízení používat</string>
+  <string name="simPINLabel">Vyžadován kód PIN karty SIM (nyní nepodporován)</string>
+  <string name="simPUKLabel">Vyžadován kód PUK karty SIM (nyní nepodporován)</string>
+  <string name="status_bar_applications_label">Aplikace</string>
+  <string name="status_bar_close">Zavřít</string>
+  <string name="status_bar_latest_events_label">Poslední události</string>
+  <string name="status_bar_no_notifications">Neupozorňovat</string>
+  <string name="status_bar_ongoing_events_label">Probíhá</string>
+  <string name="sunday">Neděle</string>
+  <string name="thursday">Čtvrtek</string>
+  <string name="time_picker_ColonBetweenHourAndMinute_text">:</string>
+  <string name="time_picker_setButton_text">Nastavit</string>
+  <string name="today">Dnes</string>
+  <string name="tomorrow">Zítra</string>
+  <string name="tuesday">Úterý</string>
+  <string name="turn_off_radio">Vypnout rádio</string>
+  <string name="turn_on_radio">Zapnout rádio</string>
+  <string name="unknownName">(neznámý)</string>
+  <string name="untitled">&lt;bez názvu&gt;</string>
+  <string name="web_user_agent">Mozilla/5.0 (Linux; U; Android 0.5; en)
+        AppleWebKit/522+ (KHTML, like Gecko) Safari/419.3</string>
+  <string name="wednesday">Středa</string>
+  <string name="week">týden</string>
+  <string name="weekly">"Týdně (%s)"</string>
+  <string name="weeks">týdnů</string>
+  <string name="yearly">Ročně</string>
+  <string name="yesterday">Včera</string>
+</resources>
diff --git a/core/res/res/values-de-rDE/strings.xml b/core/res/res/values-de-rDE/strings.xml
new file mode 100644
index 0000000..aafe7e5
--- /dev/null
+++ b/core/res/res/values-de-rDE/strings.xml
@@ -0,0 +1,767 @@
+<?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="BaMmi">Anrufsperre</string>
+  <string name="CLIRDefaultOffNextCallOff">Anrufer-ID ist standardmäßig nicht 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="CLIRDefaultOnNextCallOff">Anrufer-ID ist standardmäßig beschränkt. Nächster Anruf: Nicht beschränkt</string>
+  <string name="CLIRDefaultOnNextCallOn">Anrufer-ID ist standardmäßig beschränkt. Nächster Anruf: Beschränkt</string>
+  <string name="CLIRPermanent">Die Einstellung der Anrufer-ID kann nicht geändert werden.</string>
+  <string name="CfMmi">Rufumleitung</string>
+  <string name="ClipMmi">Anrufer-ID von eingehendem Anrufer</string>
+  <string name="ClirMmi">Anrufer-ID bei ausgehendem Anruf</string>
+  <string name="CwMmi">Anklopfen</string>
+  <string name="Midnight">"Mitternacht"</string>
+  <string name="Noon">"Mittag"</string>
+  <string name="PinMmi">PIN-Änderung</string>
+  <string name="PwdMmi">Kennwortänderung</string>
+  <string name="VideoView_error_button">OK</string>
+  <string name="VideoView_error_text_unknown">Dieses Video kann leider nicht abgespielt werden.</string>
+  <string name="VideoView_error_title">Video kann nicht abgespielt werden</string>
+  <string name="abbrev_month">"<xliff:g id="format">%b</xliff:g>"</string>
+  <string name="abbrev_month_day">"<xliff:g id="format">%b %-d</xliff:g>"</string>
+  <string name="abbrev_month_day_year">"<xliff:g id="format">%b %-d, %Y</xliff:g>"</string>
+  <string name="abbrev_month_year">"<xliff:g id="format">%b %Y</xliff:g>"</string>
+  <string name="activate_keyguard">Displaysperre</string>
+  <string name="aerr_application">Die Anwendung <xliff:g id="application">%1$s</xliff:g>
+        (Vorgang <xliff:g id="process">%2$s</xliff:g>) wurde unerwartet angehalten. Versuchen Sie es noch einmal.</string>
+  <string name="aerr_process">Der Vorgang <xliff:g id="process">%1$s</xliff:g> wurde
+        unerwartet angehalten. Versuchen Sie es noch einmal.</string>
+  <string name="aerr_title">Verzeihung!</string>
+  <string name="ago">zuvor</string>
+  <string name="alwaysUse">Standardmäßig für diese Aktion verwenden.</string>
+  <string name="am">"AM"</string>
+  <string name="anr_activity_application">Keine Reaktion bei Aktivität <xliff:g id="activity">%1$s</xliff:g> (in Anwendung <xliff:g id="application">%2$s</xliff:g>).</string>
+  <string name="anr_activity_process">Aktivität <xliff:g id="activity">%1$s</xliff:g> (in Vorgang <xliff:g id="process">%2$s</xliff:g>), keine Reaktion.</string>
+  <string name="anr_application_process">Keine Reaktion bei Anwendung <xliff:g id="application">%1$s</xliff:g> (in Vorgang <xliff:g id="process">%2$s</xliff:g>).</string>
+  <string name="anr_process">Vorgang <xliff:g id="process">%1$s</xliff:g>, keine Reaktion.</string>
+  <string name="anr_title">Keine Reaktion bei Anwendung</string>
+  <string name="badPin">Die alte PIN wurde falsch eingegeben.</string>
+  <string name="badPuk">Der PUK wurde falsch eingegeben.</string>
+  <string name="battery_low_percent_format">weniger als <xliff:g id="number">%d%%</xliff:g>
+    verbleiben.</string>
+  <string name="battery_low_subtitle">Die Akkuladung geht zur Neige:</string>
+  <string name="battery_low_title">Schließen Sie Ladegerät an</string>
+  <string name="battery_status_charging">Laden\u2026</string>
+  <string name="battery_status_text_percent_format"><xliff:g id="number">%d%%</xliff:g></string>
+  <string name="before">Vorher</string>
+  <string name="browserSavedFormData">Formulardaten gespeichert.</string>
+  <string name="byteShort">B</string>
+  <string name="cancel">Abbrechen</string>
+  <string name="capital_off">AUS</string>
+  <string name="capital_on">EIN</string>
+  <string name="cfReasonBusy">Rufumleitung - Wenn besetzt</string>
+  <string name="cfReasonNR">Rufumleitung - Wenn nicht erreichbar</string>
+  <string name="cfReasonNRy">Rufumleitung - Bei keiner Antwort</string>
+  <string name="cfReasonUnconditional">Rufumleitung - Immer</string>
+  <string name="cfTemplateForwarded">{0}: {1}</string>
+  <string name="cfTemplateForwardedTime">{0}: {1} nach {2} Sek.</string>
+  <string name="cfTemplateNotForwarded">{0}: Nicht umgeleitet</string>
+  <string name="cfTemplateRegistered">{0}: Nicht umgeleitet</string>
+  <string name="cfTemplateRegisteredTime">{0}: Nicht umgeleitet</string>
+  <string name="chooseActivity">Aktion auswählen</string>
+  <string name="clearDefaultHintMsg">Löschen Sie Standardeinstellung unter Startseiteneinstellungen &gt; Anwendungen &gt; Anwendungen verwalten.</string>
+  <string name="compass_accuracy_banner">Kompass erfordert Kalibration</string>
+  <string name="compass_accuracy_notificaction_body">Beschreiben Sie mit dem Telefon eine 8, um den Kompass zu kalibrieren.</string>
+  <string name="compass_accuracy_notificaction_title">Kompass kalibrieren</string>
+  <string name="contentServiceSync">Synchr.</string>
+  <string name="contentServiceSyncErrorNotificationDesc">Bei der Synchronisierung kommt es zu Problemen.</string>
+  <string name="contentServiceSyncNotificationDesc">Synchr.</string>
+  <string name="contentServiceSyncNotificationTitle">Synchr.</string>
+  <string name="contentServiceTooManyDeletesNotificationDesc">Zu viele %s Löschungen</string>
+  <string name="contentServiceXmppAvailable">XMPP Active</string>
+  <string name="copy">Kopieren</string>
+  <string name="copyAll">Alle kopieren</string>
+  <string name="copyUrl">URL kopieren</string>
+  <string name="cut">Ausschneiden</string>
+  <string name="cutAll">Alle ausschneiden</string>
+  <string name="daily">Täglich</string>
+  <string name="daily_format">h:mm aa</string>
+  <string name="date1_date2">"<xliff:g id="format">%2$s \u2013 %5$s</xliff:g>"</string>
+  <string name="date1_time1_date2_time2">"<xliff:g id="format">%2$s, %3$s \u2013 %5$s, %6$s</xliff:g>"</string>
+  <string name="date_picker_month">Monat</string>
+  <string name="date_picker_set">Einstellen</string>
+  <string name="date_range_separator">" \u2013 "</string>
+  <string name="date_time_set">Einstellen</string>
+  <string name="day">Tag</string>
+  <string name="day_of_week_long_friday">Freitag</string>
+  <string name="day_of_week_long_monday">Montag</string>
+  <string name="day_of_week_long_saturday">Samstag</string>
+  <string name="day_of_week_long_sunday">Sonntag</string>
+  <string name="day_of_week_long_thursday">Donnerstag</string>
+  <string name="day_of_week_long_tuesday">Dienstag</string>
+  <string name="day_of_week_long_wednesday">Mittwoch</string>
+  <string name="day_of_week_medium_friday">Fre.</string>
+  <string name="day_of_week_medium_monday">Mon.</string>
+  <string name="day_of_week_medium_saturday">Sam.</string>
+  <string name="day_of_week_medium_sunday">Son.</string>
+  <string name="day_of_week_medium_thursday">Don.</string>
+  <string name="day_of_week_medium_tuesday">Die.</string>
+  <string name="day_of_week_medium_wednesday">Mit.</string>
+  <string name="day_of_week_short_friday">Fr</string>
+  <string name="day_of_week_short_monday">Mo</string>
+  <string name="day_of_week_short_saturday">Sa</string>
+  <string name="day_of_week_short_sunday">So</string>
+  <string name="day_of_week_short_thursday">Do</string>
+  <string name="day_of_week_short_tuesday">Di</string>
+  <string name="day_of_week_short_wednesday">Mi</string>
+  <string name="day_of_week_shorter_friday">F</string>
+  <string name="day_of_week_shorter_monday">M</string>
+  <string name="day_of_week_shorter_saturday">Sa</string>
+  <string name="day_of_week_shorter_sunday">So</string>
+  <string name="day_of_week_shorter_thursday">Do</string>
+  <string name="day_of_week_shorter_tuesday">Di</string>
+  <string name="day_of_week_shorter_wednesday">M</string>
+  <string name="day_of_week_shortest_friday">F</string>
+  <string name="day_of_week_shortest_monday">M</string>
+  <string name="day_of_week_shortest_saturday">S</string>
+  <string name="day_of_week_shortest_sunday">S</string>
+  <string name="day_of_week_shortest_thursday">D</string>
+  <string name="day_of_week_shortest_tuesday">D</string>
+  <string name="day_of_week_shortest_wednesday">M</string>
+  <string name="days">Tage</string>
+  <string name="daysDurationFuturePlural">in <xliff:g id="days">%d</xliff:g> Tagen</string>
+  <string name="daysDurationPastPlural">vor <xliff:g id="days">%d</xliff:g> Tagen</string>
+  <string name="debug">Debug</string>
+  <string name="defaultMsisdnAlphaTag">MSISDN1</string>
+  <string name="defaultVoiceMailAlphaTag">Mailbox</string>
+  <string name="default_permission_group">Standard</string>
+  <string name="elapsed_time_short_format_h_mm_ss"><xliff:g id="format">%1$d:%2$02d:%3$02d</xliff:g></string>
+  <string name="elapsed_time_short_format_mm_ss"><xliff:g id="format">%1$02d:%2$02d</xliff:g></string>
+  <string name="ellipsis">\u2026</string>
+  <string name="emergency_call_dialog_call">Notruf</string>
+  <string name="emergency_call_dialog_cancel">Abbrechen</string>
+  <string name="emergency_call_dialog_number_for_display">Notrufnummer</string>
+  <string name="emergency_call_dialog_text">Notruf absetzen?</string>
+  <string name="emergency_call_number_uri">Tel:112</string>
+  <string name="emptyPhoneNumber">(Keine Telefonnummer)</string>
+  <string name="every_weekday">"Jeden Wochentag (Mon\u2013Fre)"</string>
+  <string name="factorytest_failed">Werkstest fehlgeschlagen</string>
+  <string name="factorytest_no_action">Es wurde kein Paket gefunden, dass die 
+        FACTORY_TEST-Aktion bereitstellt.</string>
+  <string name="factorytest_not_system">Die FACTORY_TEST-Aktion 
+        wird nur für Pakete unterstützt, die unter /system/app installiert sind.</string>
+  <string name="factorytest_reboot">Neustart</string>
+  <string name="force_close">Erzwungene Beendigung</string>
+  <string name="friday">Freitag</string>
+  <string name="gigabyteShort">GB</string>
+  <string name="global_action_lock">Displaysperre</string>
+  <string name="global_action_power_off">Ausschalten</string>
+  <string name="global_action_silent_mode_off_status">Sound ist EIN</string>
+  <string name="global_action_silent_mode_on_status">Sound ist AUS</string>
+  <string name="global_action_toggle_silent_mode">Lautlosmodus</string>
+  <string name="global_actions">Telefonoptionen</string>
+  <string name="hour">Stunde</string>
+  <string name="hours">Stunden</string>
+  <string name="httpError">Die Webseite enthält einen Fehler.</string>
+  <string name="httpErrorAuth">Die Authentifizierung war nicht erfolgreich.</string>
+  <string name="httpErrorBadUrl">Die Seite konnte nicht geöffnet werden, da die URL nicht gültig ist.</string>
+  <string name="httpErrorConnect">Die Verbindung zum Server war nicht erfolgreich.</string>
+  <string name="httpErrorFailedSslHandshake">Eine sichere Verbindung konnte nicht hergestellt werden.</string>
+  <string name="httpErrorFile">Zugriff auf die Datei war nicht möglich.</string>
+  <string name="httpErrorFileNotFound">Die angeforderte Datei wurde nicht gefunden.</string>
+  <string name="httpErrorIO">Kommunikation mit dem Server nicht möglich. Versuchen Sie es später noch einmal.</string>
+  <string name="httpErrorLookup">Die URL konnte nicht gefunden werden.</string>
+  <string name="httpErrorOk">OK</string>
+  <string name="httpErrorProxyAuth">Die Authentifizierung über den Proxy-Server war nicht erfolgreich.</string>
+  <string name="httpErrorRedirectLoop">Die Seite enthält zu viele Server-Umleitungen.</string>
+  <string name="httpErrorTimeout">Die Zeit für die Verbindung zum Server ist abgelaufen.</string>
+  <string name="httpErrorTooManyRequests">Es werden zu viele Aufgaben verabeitet. Versuchen Sie es später noch einmal.</string>
+  <string name="httpErrorUnsupportedAuthScheme">Das Schema für die Seitenauthentifizierung wird nicht unterstützt.</string>
+  <string name="httpErrorUnsupportedScheme">Das Protokoll wird nicht unterstützt.</string>
+  <string name="in">in</string>
+  <string name="invalidPin">Geben Sie eine PIN mit 4 bis 8 Stellen ein.</string>
+  <string name="keyguard_label_text">Drücken Sie zum Entsperren erst Menü und dann 0.</string>
+  <string name="keyguard_password_emergency_instructions">Anrufen-Taste drücken, um Notruf abzusetzen.</string>
+  <string name="keyguard_password_enter_pin_code">PIN-Code eingeben:</string>
+  <string name="keyguard_password_instructions">Passwort eingeben oder Notrufnummer wählen.</string>
+  <string name="keyguard_password_wrong_pin_code">Falscher PIN-Code!</string>
+  <string name="kilobyteShort">KB</string>
+  <string name="lockscreen_carrier_default">(Kein Dienst)</string>
+  <string name="lockscreen_carrier_key">gsm.operator.alpha</string>
+  <string name="lockscreen_emergency_call">Notruf</string>
+  <string name="lockscreen_failed_attempts_almost_glogin">
+        Sie haben das Entsperrmuster <xliff:g id="number">%d</xliff:g> Mal falsch gezeichnet.
+       Nach <xliff:g id="number">%d</xliff:g> weiteren nicht erfolgreichen Versuchen,
+       werden Sie aufgefordert das Gerät mit der Google-Anmeldung zu entsperren.\n\n
+      Versuchen Sie es bitte in <xliff:g id="number">%d</xliff:g> Sekunden noch einmal.
+    </string>
+  <string name="lockscreen_forgot_pattern_button_text">Muster vergessen?</string>
+  <string name="lockscreen_glogin_instructions">Um das Gerät zu entsperren,\nmelden Sie sich mit Ihrem Google-Konto an:</string>
+  <string name="lockscreen_glogin_invalid_input">Ungültiger Benutzername oder ungültiges Kennwort.</string>
+  <string name="lockscreen_glogin_password_hint">Kennwort</string>
+  <string name="lockscreen_glogin_submit_button">Anmelden</string>
+  <string name="lockscreen_glogin_too_many_attempts">Zu viele Musterversuche!</string>
+  <string name="lockscreen_glogin_username_hint">Benutzername (E-Mail)</string>
+  <string name="lockscreen_instructions_when_pattern_disabled">Zum Entsperren Menü drücken.</string>
+  <string name="lockscreen_instructions_when_pattern_enabled">Zum Entsperren oder Absetzen von Notruf Menü drücken.</string>
+  <string name="lockscreen_low_battery">Schließen Sie Ladegerät an.</string>
+  <string name="lockscreen_missing_sim_instructions">Bitte SIM-Karte einsetzen.</string>
+  <string name="lockscreen_missing_sim_message">Keine SIM-Karte im Telefon.</string>
+  <string name="lockscreen_missing_sim_message_short">Keine SIM-Karte.</string>
+  <string name="lockscreen_pattern_correct">Korrekt!</string>
+  <string name="lockscreen_pattern_instructions">Zum Entsperren Muster zeichnen:</string>
+  <string name="lockscreen_pattern_wrong">Versuchen Sie es erneut:</string>
+  <string name="lockscreen_plugged_in">Laden… (<xliff:g id="number">%d%%</xliff:g>)</string>
+  <string name="lockscreen_screen_locked">Display gesperrt</string>
+  <string name="lockscreen_sim_locked_message">SIM-Karte gesperrt.</string>
+  <string name="lockscreen_sim_puk_locked_instructions">Wenden Sie sich an den Kundendienst.</string>
+  <string name="lockscreen_sim_puk_locked_message">SIM-Karte ist mit PUK gesperrt.</string>
+  <string name="lockscreen_sim_unlock_progress_dialog_message">Entsperrung von SIM-Karte\u2026</string>
+  <string name="lockscreen_too_many_failed_attempts_countdown">In <xliff:g id="number">%d</xliff:g> Sekunden erneut versuchen</string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_message">
+        Sie haben das Entsperrmuster <xliff:g id="number">%d</xliff:g> Mal falsch gezeichnet.
+        \n\nIn <xliff:g id="number">%d</xliff:g> Sekunden erneut versuchen.
+    </string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_title">Warnhinweis zum Sperrmuster</string>
+  <string name="low_internal_storage_text">Interner Speicherplatz gering.</string>
+  <string name="low_internal_storage_view_text">Der interne Speicherplatz im Telefon ist gering.</string>
+  <string name="low_internal_storage_view_title">Speicherplatz gering</string>
+  <string name="low_memory">Telefonspeicher voll! Löschen Sie einige Dateien um Speicherplatz bereitzustellen.</string>
+  <string name="me">Mich</string>
+  <string name="megabyteShort">MB</string>
+  <string name="midnight">"Mitternacht"</string>
+  <string name="minute">Min</string>
+  <string name="minutes">Min</string>
+  <string name="mismatchPin">Die eingegebenen PINs stimmen nicht überein.</string>
+  <string name="mmiComplete">MMI komplett.</string>
+  <string name="mmiError">Fehler bei Verbindung oder ungültiger MMI-Code.</string>
+  <string name="monday">Montag</string>
+  <string name="month">"<xliff:g id="format">%B</xliff:g>"</string>
+  <string name="month_day">"<xliff:g id="format">%B %-d</xliff:g>"</string>
+  <string name="month_day_year">"<xliff:g id="format">%B %-d, %Y</xliff:g>"</string>
+  <string name="month_long_april">April</string>
+  <string name="month_long_august">August</string>
+  <string name="month_long_december">Dezember</string>
+  <string name="month_long_february">Februar</string>
+  <string name="month_long_january">Januar</string>
+  <string name="month_long_july">Juli</string>
+  <string name="month_long_june">Juni</string>
+  <string name="month_long_march">März</string>
+  <string name="month_long_may">Mai</string>
+  <string name="month_long_november">November</string>
+  <string name="month_long_october">Oktober</string>
+  <string name="month_long_september">September</string>
+  <string name="month_medium_april">Apr</string>
+  <string name="month_medium_august">Aug</string>
+  <string name="month_medium_december">Dez</string>
+  <string name="month_medium_february">Feb</string>
+  <string name="month_medium_january">Jan</string>
+  <string name="month_medium_july">Jul</string>
+  <string name="month_medium_june">Jun</string>
+  <string name="month_medium_march">Mär</string>
+  <string name="month_medium_may">Mai</string>
+  <string name="month_medium_november">Nov</string>
+  <string name="month_medium_october">Okt</string>
+  <string name="month_medium_september">Sep</string>
+  <string name="month_shortest_april">A</string>
+  <string name="month_shortest_august">A</string>
+  <string name="month_shortest_december">D</string>
+  <string name="month_shortest_february">F</string>
+  <string name="month_shortest_january">J</string>
+  <string name="month_shortest_july">J</string>
+  <string name="month_shortest_june">J</string>
+  <string name="month_shortest_march">M</string>
+  <string name="month_shortest_may">M</string>
+  <string name="month_shortest_november">N</string>
+  <string name="month_shortest_october">O</string>
+  <string name="month_shortest_september">S</string>
+  <string name="month_year">"<xliff:g id="format">%B %Y</xliff:g>"</string>
+  <string name="monthly">Monatlich</string>
+  <string name="monthly_format">MMM d</string>
+  <string name="more_item_label">Weitere</string>
+  <string name="needPuk2">Geben Sie die PUK2 ein, um die SIM-Karte zu entsperren.</string>
+  <string name="no">Abbrechen</string>
+  <string name="noApplications">Keine Anwendungen können diese Aktion durchführen.</string>
+  <string name="no_permissions">Keine Genehmigungen erforderlich</string>
+  <string name="no_recent_tasks">Keine letzten Anwendungen.</string>
+  <string name="noon">"Mittag"</string>
+  <string name="numeric_date">"<xliff:g id="format">%m/%d/%Y</xliff:g>"</string>
+  <string name="numeric_date_notation">"<xliff:g id="format">%m/%d/%y</xliff:g>"</string>
+  <string name="numeric_md1_md2">"<xliff:g id="format">%2$s/%3$s \u2013 %7$s/%8$s</xliff:g>"</string>
+  <string name="numeric_md1_time1_md2_time2">"<xliff:g id="format">%2$s/%3$s, %5$s \u2013 %7$s/%8$s, %10$s</xliff:g>"</string>
+  <string name="numeric_mdy1_mdy2">"<xliff:g id="format">%2$s/%3$s/%4$s \u2013 %7$s/%8$s/%9$s</xliff:g>"</string>
+  <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s/%3$s/%4$s, %5$s \u2013 %7$s/%8$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s/%3$s, %5$s \u2013 %6$s, %7$s/%8$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s/%3$s \u2013 %6$s, %7$s/%8$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s/%3$s/%4$s, %5$s \u2013 %6$s, %7$s/%8$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s/%3$s/%4$s \u2013 %6$s, %7$s/%8$s/%9$s</xliff:g>"</string>
+  <string name="ok">OK</string>
+  <string name="oneMonthDurationPast">Vor 1 Monat</string>
+  <string name="open_permission_deny">Sie haben keine Genehmigung zum Öffnen dieser Seite.</string>
+  <string name="passwordIncorrect">Falsches Kennwort.</string>
+  <string name="paste">Einfügen</string>
+  <string name="permdesc_accessCoarseLocation">Greifen Sie auf ungefähre Ortsquellen wie z. B. die
+        Funknetzdatenbank zu, um den ungefähren Ort des Telefons zu ermitteln, sofern dies möglich ist. Schädliche Anwendungen
+        können dies nutzen, um zu ermitteln, wo Sie sich ungefähr befinden.</string>
+  <string name="permdesc_accessFineLocation">Greifen Sie auf genaue Ortsquellen wie z. B. das
+        GPS auf dem Telefon zu, sofern dies möglich ist.
+        Schädliche Anwendungen können dies nutzen, um zu ermitteln, wo Sie sich befinden, und dabei
+        zusätzlichen Akkustrom verbrauchen.</string>
+  <string name="permdesc_accessMockLocation">Erstellen Sie einen Scheinort zum Testen.
+        Schädliche Anwendungen können dies nutzen, um den Ort und/oder Status zu überschreiben, der von echten
+        Ortsquellen, z. B.  GPS- oder Netzbetreibern angegeben wird.</string>
+  <string name="permdesc_accessNetworkState">Ermöglicht einer Anwendung,
+      den Zustand aller Netze zu erkennen.</string>
+  <string name="permdesc_accessSurfaceFlinger">Ermöglicht einer Anwendung
+        SurfaceFlinger-Low-Level-Funktionen zu nutzen.</string>
+  <string name="permdesc_accessWifiState">Ermöglicht einer Anwendung,
+      Informationen über den Zustand von Wi-Fi zu erkennen.</string>
+  <string name="permdesc_addSystemService">Ermöglicht einer Anwendung
+        eigene Low-Level-Systemdienste zu veröffentlichen. Schädliche Anwendungen können dies nutzen, um auf das
+        System zuzugreifen und Daten darauf zu entwenden oder zu ändern.</string>
+  <string name="permdesc_batteryStats">Ermöglicht die Änderung
+        gesammelter Akkudaten. Nicht für die Verwendung durch gewöhnliche Anwendungen bestimmt.</string>
+  <string name="permdesc_bluetooth">Ermöglicht einer Anwendung die
+      Konfiguration des lokalen Bluetooth-Telefons anzuzeigen und Verbindungen
+      mit gekoppelten Geräten anzunehmen.</string>
+  <string name="permdesc_bluetoothAdmin">Ermöglicht einer Anwendung das
+      lokale Bluetooth-Telefon zu konfigurieren und entfernte Geräte zu erkennen und zu
+      koppeln.</string>
+  <string name="permdesc_brick">Ermöglicht einer Anwendung das
+        gesamte Telefon bleibend zu aktivieren. Dies ist sehr riskant.</string>
+  <string name="permdesc_broadcastPackageRemoved">Ermöglicht einer Anwendung
+        eine Benachrichtigung zu senden, dass ein Anwendungspaket entfernt wurde.
+        Schädliche Anwendungen können dies nutzen, um alle anderen ausgeführten
+        Anwendungen zu zerstören.</string>
+  <string name="permdesc_broadcastSticky">Ermöglicht einer Anwendung
+        Übertragungen zu senden, die nach Beendigung der Sendung erhalten bleiben.
+        Schädliche Anwendungen können das Telefon langsamer oder unzuverlässiger machen, indem sie es veranlassen zu
+        viel Speicher zu nutzen.</string>
+  <string name="permdesc_callPhone">Ermöglicht einer Anwendung
+        Telefonnummern ohne Ihr Eingreifen zu wählen. Schädliche Anwendungen können dies nutzen, um
+        unerwartete Anrufe auf Ihrer Rechnung erscheinen zu lassen. Der Anwendung wird hierbei nicht
+        ermöglicht Notrufe abzusetzen.</string>
+  <string name="permdesc_camera">Ermöglicht einer Anwendung Bilder mit der
+        Kamera aufzunehmen. Die Anwendung kann jederzeit Bilder
+        aufnehmen, die von der Kamera erfasst werden.</string>
+  <string name="permdesc_changeComponentState">Ermöglicht einer Anwendung zu ändern, ob eine
+        Komponente einer anderen Anwendung aktiviert ist oder nicht. Schädliche Anwendungen können dies nutzen,
+        um wichtige Telefonfunktionen zu deaktivieren. Seien Sie mit dieser Genehmigung vorsichtig, da es möglich ist
+        Anwendungskomponenten unbrauchbar, unzuverlässig oder instabil zu machen.
+    </string>
+  <string name="permdesc_changeConfiguration">Ermöglicht einer Anwendung
+        die aktuelle Konfiguration wie z. B. die lokale oder allgemeine Schriftgröße zu
+        ändern.</string>
+  <string name="permdesc_changeNetworkState">Ermöglicht einer Anwendung,
+      den Zustand der Netzkonnektivität zu ändern.</string>
+  <string name="permdesc_changeWifiState">Ermöglicht einer Anwendung, eine Verbindung
+      mit Wi-Fi-Zugangspunkten herzustellen bzw. die Verbindung zu trennen, und Änderungen an
+      konfigurierten Wi-Fi-Netzen vorzunehmen.</string>
+  <string name="permdesc_clearAppCache">Ermöglicht einer Anwendung zusätzlichen Telefonspeicher bereitzustellen,
+        in dem sie Dateien im Cache-Verzeichnis löscht. Der Zugriff ist in der
+        Regel stark auf Systemvorgänge beschränkt.</string>
+  <string name="permdesc_clearAppUserData">Ermöglicht einer Anwendung, Benutzerdaten zu löschen.</string>
+  <string name="permdesc_createNetworkSockets">Ermöglicht einer Anwendung,
+      Netz-Sockets zu erstellen.</string>
+  <string name="permdesc_deleteCacheFiles">Ermöglicht einer Anwendung
+        Cache-Dateien zu löschen.</string>
+  <string name="permdesc_deletePackages">Ermöglicht einer Anwendung
+        Android-Pakete zu löschen. Schädliche Anwendungen können dies nutzen, um wichtige Anwendungen zu löschen.</string>
+  <string name="permdesc_devicePower">Ermöglicht einer Anwendung das
+        Telefon ein- oder auszuschalten.</string>
+  <string name="permdesc_disableKeyguard">Ermöglicht einer Anwendung,
+      die Tastensperre und einen entsprechenden Kennwortschutz zu deaktivieren. Ein typisches Beispiel hierfür ist,
+      wenn das Telefon die Tastensperre bei einem eingehenden Anruf deaktiviert
+      und nach Beendigung des Gesprächs wieder aktiviert.</string>
+  <string name="permdesc_dump">Ermöglicht einer Anwendung den
+        internen Zustand des Systems abzurufen. Schädliche Anwendungen können dies nutzen, um eine Vielzahl
+        von privaten und sicheren Daten abzurufen, die Sie normalerweise
+        niemals benötigen.</string>
+  <string name="permdesc_expandStatusBar">Ermöglicht einer Anwendung,
+        die Statusleiste zu erweitern oder zu verkleinern.</string>
+  <string name="permdesc_factoryTest">Dies wird als ein Low-Level-Herstellertest durchgeführt,
+        bei dem Sie kompletten Zugriff auf die Telefonhardware erhalten. Steht nur zur Verfügung,
+        wenn ein Telefon im Herstellertestmodus verwendet wird.</string>
+  <string name="permdesc_flashlight">Ermöglicht einer Anwendung das
+        Blitzlicht zu steuern.</string>
+  <string name="permdesc_forceBack">Ermöglicht einer Anwendung eine
+        Aktivität im Vordergrund zu beenden und in den Hintergrund treten zu lassen.
+        Für normale Anwendungen sollte dies niemals nötig sein.</string>
+  <string name="permdesc_fotaUpdate">Ermöglicht einer Anwendung
+        Benachrichtigungen über bereitstehende Systemupdates zu empfangen und deren
+        Installation auszulösen. Schädliche Anwendungen können dies nutzen, um das System mit
+        nicht autorisierten Updates zu beschädigen oder den Aktualisierungsvorgang
+        zu stören.</string>
+  <string name="permdesc_getAccounts">Ermöglicht einer Anwendung
+      die Liste mit Konten abzurufen, die dem Telefon bekannt sind.</string>
+  <string name="permdesc_getPackageSize">Ermöglicht einer Anwendung,
+        Code, Daten und Cache-Größen abzurufen.</string>
+  <string name="permdesc_getTasks">Ermöglicht einer Anwendung Daten
+        über aktuelle und ausgeführte Aufgaben abzurufen. Schädliche Anwendungen
+        können dies nutzen, um private Daten anderer Anwendungen aufzufinden.</string>
+  <string name="permdesc_hardware_test">Ermöglicht einer Anwendung verschiedene
+        Peripheriegeräte zum Testen der Hardware zu steuern.</string>
+  <string name="permdesc_injectEvents">Ermöglicht einer Anwendung 
+        eigene Eingaben (z. B. Tastendruck) auf andere Anwendungen zu übertragen. Schädliche Anwendungen 
+        können dies nutzen, um die Kontrolle über das Telefon zu erhalten.</string>
+  <string name="permdesc_installPackages">Ermöglicht einer Anwendung neue oder aktualisierte
+        Android-Pakete zu installieren. Schädliche Anwendungen können dies nutzen, um neue Anwendungen mit willkürlichen 
+        umfangreichen Genehmigungen hinzuzufügen.</string>
+  <string name="permdesc_internalSystemWindow">Ermöglicht die Erstellung von
+        Fenstern, die für die Verwendung durch die interne Systemanwenderoberfläche bestimmt sind.
+        Dies wird für normale Anwendungen nicht benötigt.</string>
+  <string name="permdesc_manageAppTokens">Ermöglicht einer Anwendung
+        eigene Tokens zu erstellen und verwalten, wobei die normale
+        Z-Reihenfolge umgangen wird. Dies sollte für normale Anwendungen nicht erforderlich sein.</string>
+  <string name="permdesc_masterClear">Ermöglicht einer Anwendung das System komplett
+        auf seine Standardwerte zurückzusetzen. Dabei werden alle Daten,
+        Konfigurationen und installierten Anwendungen gelöscht.</string>
+  <string name="permdesc_modifyAudioSettings">Ermöglicht einer Anwendung
+        globale Audioeinstellungen wie z. B. Lautstärke und Routing zu ändern.</string>
+  <string name="permdesc_modifyPhoneState">Ermöglicht einer Anwendung die
+        Telefonfunktionen des Gerätes zu steuern. Eine Anwendung mit dieser Genehmigung kann zwischen
+        Netzen wechseln, den Telefonfunk ein- und ausschalten und andere Schritte durchführen, ohne dass Sie darüber
+        unterrichtet werden.</string>
+  <string name="permdesc_mount_unmount_filesystems">Ermöglicht einer Anwendung Dateisysteme für
+         Wechselspeicher zu installieren und deinstallieren.</string>
+  <string name="permdesc_persistentActivity">Ermöglicht einer Anwendung
+        eigene Teile zu blockieren, so dass das System sie nicht für andere
+        Anwendungen nutzen kann.</string>
+  <string name="permdesc_processOutgoingCalls">Ermöglicht einer Anwendung
+        ausgehende Anrufe zu verarbeiten und die gewählte Nummer zu ändern.
+        Schädliche Anwendungen können dies nutzen, um ausgehende Anrufe zu überwachen, umzuleiten oder zu unterbinden.</string>
+  <string name="permdesc_readCalendar">Ermöglicht einer Anwendung alle
+        auf dem Telefon gespeicherten Kalenderereignisse zu lesen.
+        Schädliche Anwendungen können dies nutzen, um Kalenderereignisse anderen Personen zuzusenden.</string>
+  <string name="permdesc_readContacts">Ermöglicht einer Anwendung alle
+        auf dem Telefon gespeicherten Kontakte (Adressen) zu lesen.
+        Schädliche Anwendungen können dies nutzen, um Ihre Daten anderen Personen zuzusenden.</string>
+  <string name="permdesc_readFrameBuffer">Ermöglicht einer Anwendung den Inhalt
+        des Frame-Puffers zu lesen.</string>
+  <string name="permdesc_readInputState">Ermöglicht einer Anwendung die
+        Tasten zu erkennen, die Sie drücken, wenn Sie eine andere Anwendung nutzen
+        (z. B. wenn Sie ein Kennwort eingeben). Für normale Anwendungen sollte dies niemals erforderlich sein.</string>
+  <string name="permdesc_readLogs">Ermöglicht einer Anwendung
+        verschiedene Log-Dateien des Systems zu lesen.  Sie können so allgemeine
+        Informationen über die Aktionen auf dem Telefon erhalten. Diese Informationen sollten aber keine
+        persönlichen oder privaten Daten enthalten.</string>
+  <string name="permdesc_readOwnerData">Ermöglicht einer Anwendung
+        auf dem Telefon gespeicherte Daten des Besitzers zu lesen. Schädliche
+        Anwendungen können dies nutzen, um die Daten des Telefonbesitzers zu lesen.</string>
+  <string name="permdesc_readPhoneState">Ermöglicht der Anwendung auf  Telefonfunktionen
+        des Gerätes zuzugreifen.  Eine Anwendung mit dieser Genehmigung kann die Nummer dieses
+        Telefons ermitteln, erkennen, ob ein Gespräch geführt wird, die Nummer des Gesprächspartners erkennen 
+ usw.</string>
+  <string name="permdesc_readSms">Ermöglicht einer Anwendung
+      SMS-Nachrichten zu lesen, die auf dem Telefon oder der SIM-Karte gespeichert sind. Schädliche Anwendungen
+      können dies nutzen, um vertrauliche Nachrichten zu lesen.</string>
+  <string name="permdesc_readSyncSettings">Ermöglicht einer Anwendung Synchronisierungseinstellungen zu lesen,
+        z. B. ob die Synchronisierung für Kontakte aktiviert ist.</string>
+  <string name="permdesc_readSyncStats">Ermöglicht einer Anwendung Synchronisierungs-Statistiken,
+        z. B. den Verlauf durchgeführter Synchronisierungen, neu zu fokussieren.</string>
+  <string name="permdesc_receiveBootCompleted">Ermöglicht einer Anwendung
+        sich selbst zu starten, nachdem das System hochgefahren wurde.
+        Dies kann dazu führen, dass das Telefon langsamer gestartet wird, und die Anwendung
+        kann das Telefon allgemein verlangsamen, wenn sie jederzeit ausgeführt wird.</string>
+  <string name="permdesc_receiveMms">Ermöglicht einer Anwendung
+      MMS-Nachrichten zu empfangen und zu verarbeiten.
+      Schädliche Anwendungen können Ihre Nachrichten verfolgen oder löschen, ohne dass sie Ihnen gezeigt werden.</string>
+  <string name="permdesc_receiveSms">Ermöglicht einer Anwendung
+      SMS-Nachrichten zu empfangen und zu verarbeiten.
+      Schädliche Anwendungen können Ihre Nachrichten verfolgen oder löschen, ohne dass sie Ihnen gezeigt werden.</string>
+  <string name="permdesc_receiveWapPush">Ermöglicht einer Anwendung
+      WAP-Nachrichten zu empfangen und zu verarbeiten.
+      Schädliche Anwendungen könnenIhre Nachrichten verfolgen oder löschen, ohne dass sie Ihnen gezeigt werden.</string>
+  <string name="permdesc_recordAudio">Ermöglicht einer Anwendung 
+        auf den Audioaufnahmepfad zuzugreifen.</string>
+  <string name="permdesc_reorderTasks">Ermöglicht einer Anwendung
+        Aufgaben in den Vordergrund oder den Hintergrund zu verschieben.
+        Schädliche Anwendungen können dies nutzen, um sichin den Vordergrund zu schieben, ohne dass Sie dies verhindern können.</string>
+  <string name="permdesc_restartPackages">Ermöglicht einer Anwendung
+        den Neustart anderer Anwendungen zu erzwingen.</string>
+  <string name="permdesc_runSetActivityWatcher">Ermöglicht einer Anwendung
+        zu überwachen und zu steuern, wie das System Aktivitäten startet.
+        Schädliche Anwendungen können dies nutzen, um das komplette System zu gefährden.
+        Diese Genehmigung wird nur für die Entwicklung und niemals für die normale
+        Verwendung des Telefons benötigt.</string>
+  <string name="permdesc_sendSms">Ermöglicht Anwendungen
+      SMS-Nachrichten zu senden. Schädliche Anwendungen können dies nutzen, um zusätzliche Kosten zu verursachen,
+      wenn Nachrichten ohne Ihre Zustimmung gesendet werden.</string>
+  <string name="permdesc_setAlwaysFinish">Ermöglicht einer Anwendung zu steuern,
+        ob Aktivitäten sofort beendet werden,sobald sie in den Hintergrund treten.
+        Dies wird für normale Anwendungen niemals benötigt.</string>
+  <string name="permdesc_setAnimationScale">Ermöglicht einer Anwendung
+        die allgemeine Animationsgeschwindigkeit (schnellere oder langsamera Animationen) jederzeit zu ändern.</string>
+  <string name="permdesc_setDebugApp">Ermöglicht einer Anwendung
+        Debugging für eine andere Anwendung zu aktivieren. Schädliche Anwendungen können dies nutzen,
+        um andere Anwendungen zu zerstören.</string>
+  <string name="permdesc_setOrientation">Ermöglicht einer Anwendung die
+        Ausrichtung des Displays jederzeit zu ändern. Dies sollte für
+        normale Anwendungen niemals benötigt werden.</string>
+  <string name="permdesc_setPreferredApplications">Ermöglicht einer Anwendung
+        bevorzugte Anwendungen zu ändern. Schädliche Anwendungen können dies nutzen,
+        um die Anwendungen, die Sie verwenden, unbemerkt zu ändern und bestehende
+        Anwendungen zum Sammeln Ihrer privaten Daten zu veranlassen.</string>
+  <string name="permdesc_setProcessForeground">Ermöglicht einer Anwendung, jeden
+        Vorgang im Vordergrund ablaufen zu lassen, so dass er nicht unterbunden werden kann.
+        Für normale Anwendungen sollte dies nicht erforderlich sein.</string>
+  <string name="permdesc_setProcessLimit">Ermöglicht einer Anwendung 
+        die maximale Anzahl von Vorgängen zu steuern, die durchgeführt werden.Dies wird für
+        normale Anwendungen niemals benötigt.</string>
+  <string name="permdesc_setTimeZone">Ermöglicht einer Anwendung
+        die Zeitzone des Telefons zu ändern.</string>
+  <string name="permdesc_setWallpaper">Ermöglicht einer Anwendung
+        den Systembildschirmhintergrund einzustellen.</string>
+  <string name="permdesc_setWallpaperHints">Ermöglicht einer Anwendung
+        Hinweise für die Größe des Systembildschirmhintergrunds zu setzen.</string>
+  <string name="permdesc_signalPersistentProcesses">Ermöglicht einer Anwendung anzufordern, dass das ausgegebene
+        Signal allen anhaltenden Vorgängen zugesandt wird.</string>
+  <string name="permdesc_statusBar">Ermöglicht einer Anwendung die
+        Statusleiste zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen.</string>
+  <string name="permdesc_systemAlertWindow">Ermöglicht einer Anwendung
+        Systemwarnfenster anzuzeigen. Schädliche Anwendungen können dies nutzen,
+        um das gesamte Display des Telefons auszufüllen.</string>
+  <string name="permdesc_vibrate">Ermöglicht einer Anwendung die
+        Vibration zu steuern.</string>
+  <string name="permdesc_wakeLock">Ermöglicht einer Anwendung
+        zu verhindern, dass das Telefon den Schlafmodus aktiviert.</string>
+  <string name="permdesc_writeCalendar">Ermöglicht einer Anwendung
+        die auf dem Telefon gespeicherten Kalenderereignisse zu ändern.
+        Schädliche Anwendungen können dies nutzen, um die Kalenderdaten zu löschen oder modifizieren.</string>
+  <string name="permdesc_writeContacts">Ermöglicht einer Anwendung
+        die auf dem Telefon gespeicherten Kontaktdaten (Adressen) zu ändern.
+        Schädliche Anwendungen können dies nutzen, um die Kontaktdaten zu löschen oder modifizieren.</string>
+  <string name="permdesc_writeOwnerData">Ermöglicht einer Anwendung
+        die auf dem Telefon gespeicherten Besitzerdaten zu ändern.
+        Schädliche Anwendungen können dies nutzen, um die Benutzerdaten zu löschen oder modifizieren.</string>
+  <string name="permdesc_writeSettings">Ermöglicht einer Anwendung
+        die Einstellungen des Systems zu ändern. Schädliche Anwendungen können dies nutzen, um die Konfiguration des Systems
+        zu manipulieren.</string>
+  <string name="permdesc_writeSms">Ermöglicht einer Anwendung
+      SMS-Nachrichten zu schreiben, die auf dem Telefon oder der SIM-Karte gespeichert sind. Schädliche Anwendungen
+      können dies nutzen, um Ihre Nachrichten zu löschen.</string>
+  <string name="permdesc_writeSyncSettings">Ermöglicht einer Anwendung Synchronisierungseinstellungen zu ändern,
+        z. B. ob die Synchronisierung für Kontakte aktiviert ist.</string>
+  <string name="permgroupdesc_accounts">Greifen Sie auf verfügbare Google-Konten zu.</string>
+  <string name="permgroupdesc_costMoney">Ermöglicht einer Anwendung Vorgänge
+        durchzuführen, für die Kosten anfallen können.</string>
+  <string name="permgroupdesc_developmentTools">Funktionen werden nur von
+        Anwendungsentwicklern benötigt.</string>
+  <string name="permgroupdesc_hardwareControls">Direkter Zugriff auf Hardware
+        des Gerätes.</string>
+  <string name="permgroupdesc_location">Verfolgung des physischen Ortes</string>
+  <string name="permgroupdesc_messages">Lesen und schreiben Sie SMS,
+        E-Mails und andere Nachrichten.</string>
+  <string name="permgroupdesc_network">Ermöglicht Anwendungen auf
+        unterschiedliche Netzeigenschaften zuzugreifen.</string>
+  <string name="permgroupdesc_personalInfo">Direkter Zugriff auf Kontakte
+        und Kalenderdaten, die auf dem Gerät gespeichert sind.</string>
+  <string name="permgroupdesc_phoneCalls">Verfolgung, Aufnahme und Verarbeitung
+        von Anrufen.</string>
+  <string name="permgroupdesc_systemTools">Low-Level-Zugriff und Steuerung des
+        Systems.</string>
+  <string name="permgrouplab_accounts">Ihre Google-Konten</string>
+  <string name="permgrouplab_costMoney">Kosten Geld</string>
+  <string name="permgrouplab_developmentTools">Entwicklungstools</string>
+  <string name="permgrouplab_hardwareControls">Hardwaresteuerung</string>
+  <string name="permgrouplab_location">Eigener Ort</string>
+  <string name="permgrouplab_messages">Eigene Nachrichten</string>
+  <string name="permgrouplab_network">Netzkommunikation</string>
+  <string name="permgrouplab_personalInfo">Eigene persönliche Daten</string>
+  <string name="permgrouplab_phoneCalls">Anrufe</string>
+  <string name="permgrouplab_systemTools">Systemtools</string>
+  <string name="permissions_format"><xliff:g id="perm_line1">%1$s</xliff:g>, <xliff:g id="perm_line2">%2$s</xliff:g></string>
+  <string name="permlab_accessCoarseLocation">Ungefährer (netzbasierter) Ort</string>
+  <string name="permlab_accessFineLocation">Genauer (GPS) Ort</string>
+  <string name="permlab_accessMockLocation">Scheinortquellen für Tests</string>
+  <string name="permlab_accessNetworkState">Netzstatus anzeigen</string>
+  <string name="permlab_accessSurfaceFlinger">Auf SurfaceFlinger zugreifen</string>
+  <string name="permlab_accessWifiState">Wi-Fi-Status anzeigen</string>
+  <string name="permlab_addSystemService">Low-Level-Dienste veröffentlichen</string>
+  <string name="permlab_batteryStats">Akkustatistiken ändern</string>
+  <string name="permlab_bluetooth">Bluetooth-Verbindungen erstellen</string>
+  <string name="permlab_bluetoothAdmin">Bluetooth-Verwaltung</string>
+  <string name="permlab_brick">Telefon bleibend deaktivieren</string>
+  <string name="permlab_broadcastPackageRemoved">Als Paket entfernte Übertragung senden</string>
+  <string name="permlab_broadcastSticky">Als bleibende Übertragung senden</string>
+  <string name="permlab_callPhone">Telefonnummern direkt wählen</string>
+  <string name="permlab_camera">Bilder aufnehmen</string>
+  <string name="permlab_changeComponentState">Anwendungskomponenten aktivieren oder deaktivieren</string>
+  <string name="permlab_changeConfiguration">UI-Einstellungen ändern</string>
+  <string name="permlab_changeNetworkState">Netzkonnektivität ändern</string>
+  <string name="permlab_changeWifiState">Wi-Fi-Status ändern</string>
+  <string name="permlab_clearAppCache">Alle Cache-Daten der Anwendung löschen</string>
+  <string name="permlab_clearAppUserData">Andere Anwendungsdaten löschen</string>
+  <string name="permlab_createNetworkSockets">Kompletter Internetzugriff</string>
+  <string name="permlab_deleteCacheFiles">Cache anderer Anwendung löschen</string>
+  <string name="permlab_deletePackages">Anwendungen löschen</string>
+  <string name="permlab_devicePower">Telefon ein- oder ausschalten</string>
+  <string name="permlab_disableKeyguard">Tastensperre deaktivieren</string>
+  <string name="permlab_dump">Internen Systemstatus abrufen</string>
+  <string name="permlab_expandStatusBar">Statusleiste erweitern/verkleinern</string>
+  <string name="permlab_factoryTest">Werkstestmodus ausführen</string>
+  <string name="permlab_flashlight">Blitzlicht steuern</string>
+  <string name="permlab_forceBack">Beendigung von Anwendung erzwingen</string>
+  <string name="permlab_fotaUpdate">Systemupdates automatisch installieren</string>
+  <string name="permlab_getAccounts">Bekannte Konten auffinden</string>
+  <string name="permlab_getPackageSize">Speicherplatz für Anwendung messen</string>
+  <string name="permlab_getTasks">Ausgeführte Anwendungen abrufen</string>
+  <string name="permlab_hardware_test">Test-Hardware</string>
+  <string name="permlab_injectEvents">Tasten und Kontrollschaltflächen drücken</string>
+  <string name="permlab_installPackages">Anwendungen direkt installieren</string>
+  <string name="permlab_internalSystemWindow">Nicht autorisierte Fenster anzeigen</string>
+  <string name="permlab_manageAppTokens">Anwendungs-Token verwalten</string>
+  <string name="permlab_masterClear">System auf Standardeinstellungen zurücksetzen</string>
+  <string name="permlab_modifyAudioSettings">Audioeinstellungen ändern</string>
+  <string name="permlab_modifyPhoneState">Telefonzustand ändern</string>
+  <string name="permlab_mount_unmount_filesystems">Dateisysteme installieren und deinstallieren</string>
+  <string name="permlab_persistentActivity">Anwendung jederzeit ausführen</string>
+  <string name="permlab_processOutgoingCalls">Ausgehende Anrufe abfangen</string>
+  <string name="permlab_readCalendar">Kalenderdaten lesen</string>
+  <string name="permlab_readContacts">Kontaktdaten lesen</string>
+  <string name="permlab_readFrameBuffer">Frame-Buffer lesen</string>
+  <string name="permlab_readInputState">Aufzeichnen, was Sie eingeben und welche Aktionen Sie durchführen</string>
+  <string name="permlab_readLogs">System-Log-Dateien lesen</string>
+  <string name="permlab_readOwnerData">Besitzerdaten lesen</string>
+  <string name="permlab_readPhoneState">Telefonzustand lesen</string>
+  <string name="permlab_readSms">SMS oder MMS lesen</string>
+  <string name="permlab_readSyncSettings">Synchr.-Einstellungen lesen</string>
+  <string name="permlab_readSyncStats">Synchr.-Statistiken lesen</string>
+  <string name="permlab_receiveBootCompleted">Beim Hochfahren automatisch starten</string>
+  <string name="permlab_receiveMms">MMS empfangen</string>
+  <string name="permlab_receiveSms">SMS empfangen</string>
+  <string name="permlab_receiveWapPush">WAP empfangen</string>
+  <string name="permlab_recordAudio">Audio aufnehmen</string>
+  <string name="permlab_reorderTasks">Reihenfolge ausgeführter Anwendungen ändern</string>
+  <string name="permlab_restartPackages">Andere Anwendungen neu starten</string>
+  <string name="permlab_runSetActivityWatcher">Start aller Anwendungen überwachen und steuern</string>
+  <string name="permlab_sendSms">SMS-Nachrichten senden</string>
+  <string name="permlab_setAlwaysFinish">Alle Anwendungen im Hintergrund beenden</string>
+  <string name="permlab_setAnimationScale">Allgemeine Animationsgeschwindigkeit ändern</string>
+  <string name="permlab_setDebugApp">Anwendungs-Debugging aktivieren</string>
+  <string name="permlab_setOrientation">Displayausrichtung ändern</string>
+  <string name="permlab_setPreferredApplications">Bevorzugte Anwendungen einstellen</string>
+  <string name="permlab_setProcessForeground">Beendigung unterbinden</string>
+  <string name="permlab_setProcessLimit">Anzahl von ausgeführten Vorgängen beschränken</string>
+  <string name="permlab_setTimeZone">Zeitzone einstellen</string>
+  <string name="permlab_setWallpaper">Bildschirmhintergrund einstellen</string>
+  <string name="permlab_setWallpaperHints">Hinweise für Bildschirmhintergrundgröße einstellen</string>
+  <string name="permlab_signalPersistentProcesses">Linux-Signale zu Anwendungen senden</string>
+  <string name="permlab_statusBar">Statusleiste deaktivieren oder ändern</string>
+  <string name="permlab_systemAlertWindow">Systemstufen-Warnhinweise anzeigen</string>
+  <string name="permlab_vibrate">Vibration steuern</string>
+  <string name="permlab_wakeLock">Aktivierung des Schlafmodus auf Telefon unterbinden</string>
+  <string name="permlab_writeCalendar">Kalenderdaten schreiben</string>
+  <string name="permlab_writeContacts">Kontaktdaten schreiben</string>
+  <string name="permlab_writeOwnerData">Besitzerdaten schreiben</string>
+  <string name="permlab_writeSettings">Allgemeine Systemeinstellungen ändern</string>
+  <string name="permlab_writeSms">SMS oder MMS bearbeiten</string>
+  <string name="permlab_writeSyncSettings">Synchr.-Einstellungen schreiben</string>
+  <string name="petabyteShort">PB</string>
+  <string name="pm">"PM"</string>
+  <string name="power_dialog">Telefonoptionen</string>
+  <string name="power_off">Ausschalten</string>
+  <string name="prepend_shortcut_label">Menü+</string>
+  <string name="preposition_for_date">auf %s</string>
+  <string name="preposition_for_time">am %s</string>
+  <string name="preposition_for_year">in %s</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_picker_title">Klingelton auswählen</string>
+  <string name="ringtone_silent">Lautlos</string>
+  <string name="ringtone_unknown">Unbekannter Klingelton</string>
+  <string name="safeMode">Sicherer Modus</string>
+  <string name="same_month_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s</xliff:g>"</string>
+  <string name="same_month_md1_time1_md2_time2">"<xliff:g id="format">%2$s %3$s, %5$s \u2013 %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_month_mdy1_mdy2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s, %9$s</xliff:g>"</string>
+  <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s %3$s, %4$s, %5$s \u2013 %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %5$s \u2013 %6$s, %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s, %5$s \u2013 %6$s, %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s \u2013 %6$s, %7$s %8$s, %9$s</xliff:g>"</string>
+  <string name="same_year_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %7$s %8$s</xliff:g>"</string>
+  <string name="same_year_md1_time1_md2_time2">"<xliff:g id="format">%2$s %3$s, %5$s \u2013 %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_year_mdy1_mdy2">"<xliff:g id="format">%2$s %3$s \u2013 %7$s %8$s, %9$s</xliff:g>"</string>
+  <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s %3$s, %4$s, %5$s \u2013 %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %5$s \u2013 %6$s, %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s, %5$s \u2013 %6$s, %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s, %9$s</xliff:g>"</string>
+  <string name="saturday">Samstag</string>
+  <string name="save_password_label">Bestätigen</string>
+  <string name="save_password_message">Möchten Sie, dass der Browser dieses Kennwort speichert?</string>
+  <string name="save_password_never">Niemals</string>
+  <string name="save_password_notnow">Jetzt nicht</string>
+  <string name="save_password_remember">Erinnern</string>
+  <string name="screen_lock">Displaysperre</string>
+  <string name="screen_progress">Verarbeitung\u2026</string>
+  <string name="search_go">Suchen</string>
+  <string name="second">Sek.</string>
+  <string name="seconds">Sek.</string>
+  <string name="selectAll">Alles auswählen</string>
+  <string name="selectMenuLabel">Auswählen</string>
+  <string name="select_character">Zeichen zum Einfügen auswählen</string>
+  <string name="sendText">Aktion für Text auswählen</string>
+  <string name="serviceClassData">Daten</string>
+  <string name="serviceClassDataAsync">Async</string>
+  <string name="serviceClassDataSync">Synchr.</string>
+  <string name="serviceClassFAX">FAX</string>
+  <string name="serviceClassPAD">PAD</string>
+  <string name="serviceClassPacket">Paket</string>
+  <string name="serviceClassSMS">SMS</string>
+  <string name="serviceClassVoice">Sprachnotiz</string>
+  <string name="serviceDisabled">Dienst wurde deaktiviert.</string>
+  <string name="serviceEnabled">Dienst wurde aktiviert.</string>
+  <string name="serviceEnabledFor">Dienst wurde aktiviert für:</string>
+  <string name="serviceErased">Löschen war erfolgreich.</string>
+  <string name="serviceNotProvisioned">Dienst nicht verfügbar.</string>
+  <string name="serviceRegistered">Registrierung war erfolgreich.</string>
+  <string name="shutdown_confirm">Das Telefon wird ausgeschaltet.</string>
+  <string name="shutdown_progress">Beendigung\u2026</string>
+  <string name="silent_mode">Lautlosmodus</string>
+  <string name="simAbsentLabel">SIM-Karte nicht vorhanden oder falsch eingesetzt.</string>
+  <string name="simNetworkPersonalizationLabel">SIM-Karte kann auf diesem Telefon nicht verwendet werden.</string>
+  <string name="simPINLabel">SIM PIN erforderlich (und wird derzeit nicht unterstützt).</string>
+  <string name="simPUKLabel">SIM PUK erforderlich (und wird derzeit nicht unterstützt).</string>
+  <string name="sms_control_message">Es werden eine große Anzahl von SMS-Nachrichten gesendet. Wählen Sie \"OK\", um fortzufahren, oder \"Abbrechen\", um das Senden zu beenden.</string>
+  <string name="sms_control_no">Abbrechen</string>
+  <string name="sms_control_title">Sendung von SMS-Nachrichten</string>
+  <string name="sms_control_yes">OK</string>
+  <string name="status_bar_applications_title">Anwendung</string>
+  <string name="status_bar_clear_all_button">Benachrichtigungen löschen</string>
+  <string name="status_bar_date_format">"<xliff:g id="format">MMMM T, JJJJ</xliff:g>"</string>
+  <string name="status_bar_latest_events_title">Benachrichtigungen</string>
+  <string name="status_bar_no_notifications_title">Keine Benachrichtigungen</string>
+  <string name="status_bar_ongoing_events_title">Laufend</string>
+  <string name="status_bar_time_format">"<xliff:g id="format">h:mm AA</xliff:g>"</string>
+  <string name="sunday">Sonntag</string>
+  <string name="terabyteShort">TB</string>
+  <string name="text_copied">In Zwischenablage kopierter Text.</string>
+  <string name="thursday">Donnerstag</string>
+  <string name="time1_time2">"<xliff:g id="format">%1$s \u2013 %2$s</xliff:g>"</string>
+  <string name="time_date">"<xliff:g id="format">%1$s, %3$s</xliff:g>"</string>
+  <string name="time_picker_set">Einstellen</string>
+  <string name="time_wday">"<xliff:g id="format">%1$s, %2$s</xliff:g>"</string>
+  <string name="time_wday_date">"<xliff:g id="format">%1$s, %2$s, %3$s</xliff:g>"</string>
+  <string name="today">Heute</string>
+  <string name="tomorrow">Morgen</string>
+  <string name="tuesday">Dienstag</string>
+  <string name="turn_off_radio">Wireless ausschalten</string>
+  <string name="turn_on_radio">Wireless einschalten</string>
+  <string name="unknownName">(Unbekannt)</string>
+  <string name="untitled">&lt;ohne Titel&gt;</string>
+  <string name="volume_alarm">Weckerlautstärke</string>
+  <string name="volume_call">Lautstärke während Anruf</string>
+  <string name="volume_music">Musik-/Videolautstärke</string>
+  <string name="volume_ringtone">Klingellautstärke</string>
+  <string name="volume_unknown">Lautstärke</string>
+  <string name="wait">Warten</string>
+  <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="format">%1$s, %2$s, %3$s \u2013 %4$s, %5$s, %6$s</xliff:g>"</string>
+  <string name="wday1_date1_wday2_date2">"<xliff:g id="format">%1$s, %2$s \u2013 %4$s, %5$s</xliff:g>"</string>
+  <string name="wday_date">"<xliff:g id="format">%2$s, %3$s</xliff:g>"</string>
+  <string name="web_user_agent"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android 0,6; %s)
+        AppleWebKit/525.10+ (KHTML, z. B. Gecko) Version/3.0,4 Mobile Safari/523.12,2</xliff:g></string>
+  <string name="wednesday">Mittwoch</string>
+  <string name="week">Woche</string>
+  <string name="weekly">"Wöchentlich am <xliff:g id="day">%s</xliff:g>"</string>
+  <string name="weekly_format">MMM T</string>
+  <string name="weeks">Wochen</string>
+  <string name="whichApplication">Aktion durchführen mit</string>
+  <string name="year">Jahr</string>
+  <string name="yearly">Jährlich</string>
+  <string name="yearly_format">JJJJ</string>
+  <string name="years">Jahre</string>
+  <string name="yes">OK</string>
+  <string name="yesterday">Gestern</string>
+</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..c8260b17
--- /dev/null
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -0,0 +1,767 @@
+<?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="BaMmi">Call barring</string>
+  <string name="CLIRDefaultOffNextCallOff">Caller ID defaults to not restricted. Next call: Not restricted</string>
+  <string name="CLIRDefaultOffNextCallOn">Caller ID defaults to not restricted. Next call: Restricted</string>
+  <string name="CLIRDefaultOnNextCallOff">Caller ID defaults to restricted. Next call: Not restricted</string>
+  <string name="CLIRDefaultOnNextCallOn">Caller ID defaults to restricted. Next call: Restricted</string>
+  <string name="CLIRPermanent">The caller ID setting cannot be changed.</string>
+  <string name="CfMmi">Call forwarding</string>
+  <string name="ClipMmi">Incoming Caller ID</string>
+  <string name="ClirMmi">Outgoing Caller ID</string>
+  <string name="CwMmi">Call waiting</string>
+  <string name="Midnight">"Midnight"</string>
+  <string name="Noon">"Noon"</string>
+  <string name="PinMmi">PIN change</string>
+  <string name="PwdMmi">Password change</string>
+  <string name="VideoView_error_button">OK</string>
+  <string name="VideoView_error_text_unknown">Sorry, this video cannot be played.</string>
+  <string name="VideoView_error_title">Cannot play video</string>
+  <string name="abbrev_month">"<xliff:g id="format">%b</xliff:g>"</string>
+  <string name="abbrev_month_day">"<xliff:g id="format">%-d %b</xliff:g>"</string>
+  <string name="abbrev_month_day_year">"<xliff:g id="format">%-d %b, %Y</xliff:g>"</string>
+  <string name="abbrev_month_year">"<xliff:g id="format">%b %Y</xliff:g>"</string>
+  <string name="activate_keyguard">Screen lock</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="aerr_title">Sorry!</string>
+  <string name="ago">ago</string>
+  <string name="alwaysUse">Use by default for this action.</string>
+  <string name="am">"AM"</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="anr_title">Application unresponsive</string>
+  <string name="badPin">The old PIN you typed is not correct.</string>
+  <string name="badPuk">The PUK you typed is not correct.</string>
+  <string name="battery_low_percent_format">less than <xliff:g id="number">%d%%</xliff:g>
+    remaining.</string>
+  <string name="battery_low_subtitle">The battery is getting low:</string>
+  <string name="battery_low_title">Please connect charger</string>
+  <string name="battery_status_charging">Charging\u2026</string>
+  <string name="battery_status_text_percent_format"><xliff:g id="number">%d%%</xliff:g></string>
+  <string name="before">Before</string>
+  <string name="browserSavedFormData">Saved form data.</string>
+  <string name="byteShort">B</string>
+  <string name="cancel">Cancel</string>
+  <string name="capital_off">OFF</string>
+  <string name="capital_on">ON</string>
+  <string name="cfReasonBusy">Call forwarding - Busy</string>
+  <string name="cfReasonNR">Call forwarding - Not reachable</string>
+  <string name="cfReasonNRy">Call forwarding - No reply</string>
+  <string name="cfReasonUnconditional">Call forwarding - Always</string>
+  <string name="cfTemplateForwarded">{0}: {1}</string>
+  <string name="cfTemplateForwardedTime">{0}: {1} after {2} seconds</string>
+  <string name="cfTemplateNotForwarded">{0}: Not forwarded</string>
+  <string name="cfTemplateRegistered">{0}: Not forwarded</string>
+  <string name="cfTemplateRegisteredTime">{0}: Not forwarded</string>
+  <string name="chooseActivity">Select an action</string>
+  <string name="clearDefaultHintMsg">Clear default in Home Settings &gt; Applications &gt; Manage applications.</string>
+  <string name="compass_accuracy_banner">Compass requires calibration</string>
+  <string name="compass_accuracy_notificaction_body">Rotate phone in figure 8 patterns to calibrate compass.</string>
+  <string name="compass_accuracy_notificaction_title">Calibrate compass</string>
+  <string name="contentServiceSync">Sync</string>
+  <string name="contentServiceSyncErrorNotificationDesc">Sync is experiencing problems.</string>
+  <string name="contentServiceSyncNotificationDesc">Syncing</string>
+  <string name="contentServiceSyncNotificationTitle">Sync</string>
+  <string name="contentServiceTooManyDeletesNotificationDesc">Too many %s deletes.</string>
+  <string name="contentServiceXmppAvailable">XMPP Active</string>
+  <string name="copy">Copy</string>
+  <string name="copyAll">Copy all</string>
+  <string name="copyUrl">Copy URL</string>
+  <string name="cut">Cut</string>
+  <string name="cutAll">Cut all</string>
+  <string name="daily">Daily</string>
+  <string name="daily_format">h:mm aa</string>
+  <string name="date1_date2">"<xliff:g id="format">%2$s \u2013 %5$s</xliff:g>"</string>
+  <string name="date1_time1_date2_time2">"<xliff:g id="format">%2$s, %3$s \u2013 %5$s, %6$s</xliff:g>"</string>
+  <string name="date_picker_month">month</string>
+  <string name="date_picker_set">Set</string>
+  <string name="date_range_separator">" \u2013 "</string>
+  <string name="date_time_set">Set</string>
+  <string name="day">day</string>
+  <string name="day_of_week_long_friday">Friday</string>
+  <string name="day_of_week_long_monday">Monday</string>
+  <string name="day_of_week_long_saturday">Saturday</string>
+  <string name="day_of_week_long_sunday">Sunday</string>
+  <string name="day_of_week_long_thursday">Thursday</string>
+  <string name="day_of_week_long_tuesday">Tuesday</string>
+  <string name="day_of_week_long_wednesday">Wednesday</string>
+  <string name="day_of_week_medium_friday">Fri</string>
+  <string name="day_of_week_medium_monday">Mon</string>
+  <string name="day_of_week_medium_saturday">Sat</string>
+  <string name="day_of_week_medium_sunday">Sun</string>
+  <string name="day_of_week_medium_thursday">Thu</string>
+  <string name="day_of_week_medium_tuesday">Tue</string>
+  <string name="day_of_week_medium_wednesday">Wed</string>
+  <string name="day_of_week_short_friday">Fr</string>
+  <string name="day_of_week_short_monday">Mo</string>
+  <string name="day_of_week_short_saturday">Sa</string>
+  <string name="day_of_week_short_sunday">Su</string>
+  <string name="day_of_week_short_thursday">Th</string>
+  <string name="day_of_week_short_tuesday">Tu</string>
+  <string name="day_of_week_short_wednesday">We</string>
+  <string name="day_of_week_shorter_friday">F</string>
+  <string name="day_of_week_shorter_monday">M</string>
+  <string name="day_of_week_shorter_saturday">Sa</string>
+  <string name="day_of_week_shorter_sunday">Su</string>
+  <string name="day_of_week_shorter_thursday">Th</string>
+  <string name="day_of_week_shorter_tuesday">Tu</string>
+  <string name="day_of_week_shorter_wednesday">W</string>
+  <string name="day_of_week_shortest_friday">F</string>
+  <string name="day_of_week_shortest_monday">M</string>
+  <string name="day_of_week_shortest_saturday">S</string>
+  <string name="day_of_week_shortest_sunday">S</string>
+  <string name="day_of_week_shortest_thursday">T</string>
+  <string name="day_of_week_shortest_tuesday">T</string>
+  <string name="day_of_week_shortest_wednesday">W</string>
+  <string name="days">days</string>
+  <string name="daysDurationFuturePlural">in <xliff:g id="days">%d</xliff:g> days</string>
+  <string name="daysDurationPastPlural"><xliff:g id="days">%d</xliff:g> days ago</string>
+  <string name="debug">Debug</string>
+  <string name="defaultMsisdnAlphaTag">MSISDN1</string>
+  <string name="defaultVoiceMailAlphaTag">Voicemail</string>
+  <string name="default_permission_group">Default</string>
+  <string name="elapsed_time_short_format_h_mm_ss"><xliff:g id="format">%1$d:%2$02d:%3$02d</xliff:g></string>
+  <string name="elapsed_time_short_format_mm_ss"><xliff:g id="format">%1$02d:%2$02d</xliff:g></string>
+  <string name="ellipsis">\u2026</string>
+  <string name="emergency_call_dialog_call">Emergency call</string>
+  <string name="emergency_call_dialog_cancel">Cancel</string>
+  <string name="emergency_call_dialog_number_for_display">Emergency number</string>
+  <string name="emergency_call_dialog_text">Make an emergency call?</string>
+  <string name="emergency_call_number_uri">tel:112</string>
+  <string name="emptyPhoneNumber">(No phone number)</string>
+  <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
+  <string name="factorytest_failed">Factory test failed</string>
+  <string name="factorytest_no_action">No package was found that provides the
+        FACTORY_TEST action.</string>
+  <string name="factorytest_not_system">The FACTORY_TEST action
+        is only supported for packages installed in /system/app.</string>
+  <string name="factorytest_reboot">Reboot</string>
+  <string name="force_close">Force close</string>
+  <string name="friday">Friday</string>
+  <string name="gigabyteShort">GB</string>
+  <string name="global_action_lock">Screen lock</string>
+  <string name="global_action_power_off">Power off</string>
+  <string name="global_action_silent_mode_off_status">Sound is ON</string>
+  <string name="global_action_silent_mode_on_status">Sound is OFF</string>
+  <string name="global_action_toggle_silent_mode">Silent mode</string>
+  <string name="global_actions">Phone options</string>
+  <string name="hour">hour</string>
+  <string name="hours">hours</string>
+  <string name="httpError">The Web page contains an error.</string>
+  <string name="httpErrorAuth">Authentication was unsuccessful.</string>
+  <string name="httpErrorBadUrl">The page could not be opened because the URL is invalid.</string>
+  <string name="httpErrorConnect">The connection to the server was unsuccessful.</string>
+  <string name="httpErrorFailedSslHandshake">A secure connection could not be established.</string>
+  <string name="httpErrorFile">The file could not be accessed.</string>
+  <string name="httpErrorFileNotFound">The requested file was not found.</string>
+  <string name="httpErrorIO">The server failed to communicate. Try again later.</string>
+  <string name="httpErrorLookup">The URL could not be found.</string>
+  <string name="httpErrorOk">OK</string>
+  <string name="httpErrorProxyAuth">Authentication via the proxy server was unsuccessful.</string>
+  <string name="httpErrorRedirectLoop">The page contains too many server redirects.</string>
+  <string name="httpErrorTimeout">The connection to the server timed out.</string>
+  <string name="httpErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
+  <string name="httpErrorUnsupportedAuthScheme">The site authentication scheme is not supported.</string>
+  <string name="httpErrorUnsupportedScheme">The protocol is not supported.</string>
+  <string name="in">in</string>
+  <string name="invalidPin">Type a PIN that is 4 to 8 numbers.</string>
+  <string name="keyguard_label_text">To unlock, press Menu then 0.</string>
+  <string name="keyguard_password_emergency_instructions">Press the Call button to make an emergency call.</string>
+  <string name="keyguard_password_enter_pin_code">Enter PIN code:</string>
+  <string name="keyguard_password_instructions">Enter passcode or dial emergency number.</string>
+  <string name="keyguard_password_wrong_pin_code">Incorrect PIN code!</string>
+  <string name="kilobyteShort">KB</string>
+  <string name="lockscreen_carrier_default">(No service)</string>
+  <string name="lockscreen_carrier_key">gsm.operator.alpha</string>
+  <string name="lockscreen_emergency_call">Emergency call</string>
+  <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>
+  <string name="lockscreen_forgot_pattern_button_text">Forgot pattern?</string>
+  <string name="lockscreen_glogin_instructions">To unlock,\nsign in with your Google account:</string>
+  <string name="lockscreen_glogin_invalid_input">Invalid username or password.</string>
+  <string name="lockscreen_glogin_password_hint">Password</string>
+  <string name="lockscreen_glogin_submit_button">Sign in</string>
+  <string name="lockscreen_glogin_too_many_attempts">Too many pattern attempts!</string>
+  <string name="lockscreen_glogin_username_hint">Username (email)</string>
+  <string name="lockscreen_instructions_when_pattern_disabled">Press Menu to unlock.</string>
+  <string name="lockscreen_instructions_when_pattern_enabled">Press Menu to unlock or place emergency call.</string>
+  <string name="lockscreen_low_battery">Connect your charger.</string>
+  <string name="lockscreen_missing_sim_instructions">Please insert a SIM card.</string>
+  <string name="lockscreen_missing_sim_message">No SIM card in phone.</string>
+  <string name="lockscreen_missing_sim_message_short">No SIM card.</string>
+  <string name="lockscreen_pattern_correct">Correct!</string>
+  <string name="lockscreen_pattern_instructions">Draw pattern to unlock:</string>
+  <string name="lockscreen_pattern_wrong">Sorry, try again:</string>
+  <string name="lockscreen_plugged_in">Charging (<xliff:g id="number">%d%%</xliff:g>)</string>
+  <string name="lockscreen_screen_locked">Screen locked</string>
+  <string name="lockscreen_sim_locked_message">SIM card is locked.</string>
+  <string name="lockscreen_sim_puk_locked_instructions">Please contact Customer Care.</string>
+  <string name="lockscreen_sim_puk_locked_message">SIM card is PUK-locked.</string>
+  <string name="lockscreen_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+  <string name="lockscreen_too_many_failed_attempts_countdown">Try again in <xliff:g id="number">%d</xliff:g> seconds.</string>
+  <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>
+  <string name="lockscreen_too_many_failed_attempts_dialog_title">Lock pattern warning</string>
+  <string name="low_internal_storage_text">Low on internal storage space.</string>
+  <string name="low_internal_storage_view_text">Your phone is running low on internal storage space.</string>
+  <string name="low_internal_storage_view_title">Low on space</string>
+  <string name="low_memory">Phone storage is full! Delete some files to free space.</string>
+  <string name="me">Me</string>
+  <string name="megabyteShort">MB</string>
+  <string name="midnight">"midnight"</string>
+  <string name="minute">min</string>
+  <string name="minutes">mins</string>
+  <string name="mismatchPin">The PINs you entered do not match.</string>
+  <string name="mmiComplete">MMI complete.</string>
+  <string name="mmiError">Connection problem or invalid MMI code.</string>
+  <string name="monday">Monday</string>
+  <string name="month">"<xliff:g id="format">%B</xliff:g>"</string>
+  <string name="month_day">"<xliff:g id="format">%-d %B</xliff:g>"</string>
+  <string name="month_day_year">"<xliff:g id="format">%-d %B, %Y</xliff:g>"</string>
+  <string name="month_long_april">April</string>
+  <string name="month_long_august">August</string>
+  <string name="month_long_december">December</string>
+  <string name="month_long_february">February</string>
+  <string name="month_long_january">January</string>
+  <string name="month_long_july">July</string>
+  <string name="month_long_june">June</string>
+  <string name="month_long_march">March</string>
+  <string name="month_long_may">May</string>
+  <string name="month_long_november">November</string>
+  <string name="month_long_october">October</string>
+  <string name="month_long_september">September</string>
+  <string name="month_medium_april">Apr</string>
+  <string name="month_medium_august">Aug</string>
+  <string name="month_medium_december">Dec</string>
+  <string name="month_medium_february">Feb</string>
+  <string name="month_medium_january">Jan</string>
+  <string name="month_medium_july">Jul</string>
+  <string name="month_medium_june">Jun</string>
+  <string name="month_medium_march">Mar</string>
+  <string name="month_medium_may">May</string>
+  <string name="month_medium_november">Nov</string>
+  <string name="month_medium_october">Oct</string>
+  <string name="month_medium_september">Sep</string>
+  <string name="month_shortest_april">A</string>
+  <string name="month_shortest_august">A</string>
+  <string name="month_shortest_december">D</string>
+  <string name="month_shortest_february">F</string>
+  <string name="month_shortest_january">J</string>
+  <string name="month_shortest_july">J</string>
+  <string name="month_shortest_june">J</string>
+  <string name="month_shortest_march">M</string>
+  <string name="month_shortest_may">M</string>
+  <string name="month_shortest_november">N</string>
+  <string name="month_shortest_october">O</string>
+  <string name="month_shortest_september">S</string>
+  <string name="month_year">"<xliff:g id="format">%B %Y</xliff:g>"</string>
+  <string name="monthly">Monthly</string>
+  <string name="monthly_format">d MMM</string>
+  <string name="more_item_label">More</string>
+  <string name="needPuk2">Type PUK2 to unblock SIM card.</string>
+  <string name="no">Cancel</string>
+  <string name="noApplications">No applications can perform this action.</string>
+  <string name="no_permissions">No permissions required</string>
+  <string name="no_recent_tasks">No recent applications.</string>
+  <string name="noon">"noon"</string>
+  <string name="numeric_date">"<xliff:g id="format">%d/%m/%Y</xliff:g>"</string>
+  <string name="numeric_date_notation">"<xliff:g id="format">%d/%m/%y</xliff:g>"</string>
+  <string name="numeric_md1_md2">"<xliff:g id="format">%2$s/%3$s \u2013 %7$s/%8$s</xliff:g>"</string>
+  <string name="numeric_md1_time1_md2_time2">"<xliff:g id="format">%2$s/%3$s, %5$s \u2013 %7$s/%8$s, %10$s</xliff:g>"</string>
+  <string name="numeric_mdy1_mdy2">"<xliff:g id="format">%2$s/%3$s/%4$s \u2013 %7$s/%8$s/%9$s</xliff:g>"</string>
+  <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s/%3$s/%4$s, %5$s \u2013 %7$s/%8$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s/%3$s, %5$s \u2013 %6$s, %7$s/%8$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s/%3$s \u2013 %6$s, %7$s/%8$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s/%3$s/%4$s, %5$s \u2013 %6$s, %7$s/%8$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s/%3$s/%4$s \u2013 %6$s, %7$s/%8$s/%9$s</xliff:g>"</string>
+  <string name="ok">OK</string>
+  <string name="oneMonthDurationPast">1 month ago</string>
+  <string name="open_permission_deny">You do not have permission to open this page.</string>
+  <string name="passwordIncorrect">Incorrect password.</string>
+  <string name="paste">Paste</string>
+  <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>
+  <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>
+  <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>
+  <string name="permdesc_accessNetworkState">Allows an application to view
+      the state of all networks.</string>
+  <string name="permdesc_accessSurfaceFlinger">Allows application to use
+        SurfaceFlinger low-level features.</string>
+  <string name="permdesc_accessWifiState">Allows an application to view
+      the information about the state of Wi-Fi.</string>
+  <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>
+  <string name="permdesc_batteryStats">Allows the modification of
+        collected battery statistics. Not for use by normal applications.</string>
+  <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>
+  <string name="permdesc_bluetoothAdmin">Allows an application to configure
+      the local Bluetooth phone, and to discover and pair with remote
+      devices.</string>
+  <string name="permdesc_brick">Allows the application to
+        disable the entire phone permanently. This is very dangerous.</string>
+  <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>
+  <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>
+  <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>
+  <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>
+  <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, inconsistant, or unstable state.
+    </string>
+  <string name="permdesc_changeConfiguration">Allows an application to
+        change the current configuration, such as the locale or overall font
+        size.</string>
+  <string name="permdesc_changeNetworkState">Allows an application to change
+      the state network connectivity.</string>
+  <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>
+  <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>
+  <string name="permdesc_clearAppUserData">Allows an application to clear user data.</string>
+  <string name="permdesc_createNetworkSockets">Allows an application to
+      create network sockets.</string>
+  <string name="permdesc_deleteCacheFiles">Allows an application to delete
+        cache files.</string>
+  <string name="permdesc_deletePackages">Allows an application to delete
+        Android packages. Malicious applications can use this to delete important applications.</string>
+  <string name="permdesc_devicePower">Allows the application to turn the
+        phone on or off.</string>
+  <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>
+  <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>
+  <string name="permdesc_expandStatusBar">Allows application to
+        expand or collapse the status bar.</string>
+  <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>
+  <string name="permdesc_flashlight">Allows the application to control
+        the flashlight.</string>
+  <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>
+  <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>
+  <string name="permdesc_getAccounts">Allows an application to get
+      the list of accounts known by the phone.</string>
+  <string name="permdesc_getPackageSize">Allows an application to retrieve
+        its code, data, and cache sizes</string>
+  <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>
+  <string name="permdesc_hardware_test">Allows the application to control
+        various peripherals for the purpose of hardware testing.</string>
+  <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>
+  <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>
+  <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>
+  <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>
+  <string name="permdesc_masterClear">Allows an application to completely
+        reset the system to its factory settings, erasing all data,
+        configuration, and installed applications.</string>
+  <string name="permdesc_modifyAudioSettings">Allows application to modify
+        global audio settings such as volume and routing.</string>
+  <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>
+  <string name="permdesc_mount_unmount_filesystems">Allows the application to mount and
+        unmount filesystems for removable storage.</string>
+  <string name="permdesc_persistentActivity">Allows an application to make
+        parts of itself persistent, so the system can't use it for other
+        applications.</string>
+  <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>
+  <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>
+  <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>
+  <string name="permdesc_readFrameBuffer">Allows application to use
+        read the content of the frame buffer.</string>
+  <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>
+  <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>
+  <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>
+  <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>
+  <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>
+  <string name="permdesc_readSyncSettings">Allows an application to read the sync settings,
+        such as whether sync is enabled for Contacts.</string>
+  <string name="permdesc_readSyncStats">Allows an application to reafocusd the sync stats; e.g., the
+        history of syncs that have occurred.</string>
+  <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>
+  <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>
+  <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>
+  <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>
+  <string name="permdesc_recordAudio">Allows application to access
+        the audio record path.</string>
+  <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>
+  <string name="permdesc_restartPackages">Allows an application to
+        forcibly restart other applications.</string>
+  <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>
+  <string name="permdesc_sendSms">Allows application to send SMS
+      messages. Malicious applications may cost you money by sending
+      messages without your confirmation.</string>
+  <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>
+  <string name="permdesc_setAnimationScale">Allows an application to change
+        the global animation speed (faster or slower animations) at any time.</string>
+  <string name="permdesc_setDebugApp">Allows an application to turn
+        on debugging for another application. Malicious applications can use this
+        to kill other applications.</string>
+  <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>
+  <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>
+  <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>
+  <string name="permdesc_setProcessLimit">Allows an application
+        to control the maximum number of processes that will run. Never
+        needed for normal applications.</string>
+  <string name="permdesc_setTimeZone">Allows an application to change
+        the phone's time zone.</string>
+  <string name="permdesc_setWallpaper">Allows the application
+        to set the system wallpaper.</string>
+  <string name="permdesc_setWallpaperHints">Allows the application
+        to set the system wallpaper size hints.</string>
+  <string name="permdesc_signalPersistentProcesses">Allows application to request that the
+        supplied signal be sent to all persistent processes.</string>
+  <string name="permdesc_statusBar">Allows application to disable
+        the status bar or add and remove system icons.</string>
+  <string name="permdesc_systemAlertWindow">Allows an application to
+        show system alert windows. Malicious applications can take over the
+        entire screen of the phone.</string>
+  <string name="permdesc_vibrate">Allows the application to control
+        the vibrator.</string>
+  <string name="permdesc_wakeLock">Allows an application to prevent
+        the phone from going to sleep.</string>
+  <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>
+  <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>
+  <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>
+  <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="permdesc_writeSms">Allows application to write
+      to SMS messages stored on your phone or SIM card. Malicious applications
+      may delete your messages.</string>
+  <string name="permdesc_writeSyncSettings">Allows an application to modify the sync
+        settings, such as whether sync is enabled for Contacts.</string>
+  <string name="permgroupdesc_accounts">Access the available Google accounts.</string>
+  <string name="permgroupdesc_costMoney">Allow applications to do things
+        that can cost you money.</string>
+  <string name="permgroupdesc_developmentTools">Features only needed for
+        application developers.</string>
+  <string name="permgroupdesc_hardwareControls">Direct access to hardware on
+        the handset.</string>
+  <string name="permgroupdesc_location">Monitor your physical location</string>
+  <string name="permgroupdesc_messages">Read and write your SMS,
+        e-mail, and other messages.</string>
+  <string name="permgroupdesc_network">Allow applications to access
+        various network features.</string>
+  <string name="permgroupdesc_personalInfo">Direct access to your contacts
+        and calendar stored on the phone.</string>
+  <string name="permgroupdesc_phoneCalls">Monitor, record, and process
+        phone calls.</string>
+  <string name="permgroupdesc_systemTools">Lower-level access and control
+        of the system.</string>
+  <string name="permgrouplab_accounts">Your Google accounts</string>
+  <string name="permgrouplab_costMoney">Cost you money</string>
+  <string name="permgrouplab_developmentTools">Development tools</string>
+  <string name="permgrouplab_hardwareControls">Hardware controls</string>
+  <string name="permgrouplab_location">Your location</string>
+  <string name="permgrouplab_messages">Your messages</string>
+  <string name="permgrouplab_network">Network communication</string>
+  <string name="permgrouplab_personalInfo">Your personal information</string>
+  <string name="permgrouplab_phoneCalls">Phone calls</string>
+  <string name="permgrouplab_systemTools">System tools</string>
+  <string name="permissions_format"><xliff:g id="perm_line1">%1$s</xliff:g>, <xliff:g id="perm_line2">%2$s</xliff:g></string>
+  <string name="permlab_accessCoarseLocation">coarse (network-based) location</string>
+  <string name="permlab_accessFineLocation">fine (GPS) location</string>
+  <string name="permlab_accessMockLocation">mock location sources for testing</string>
+  <string name="permlab_accessNetworkState">view network state</string>
+  <string name="permlab_accessSurfaceFlinger">access SurfaceFlinger</string>
+  <string name="permlab_accessWifiState">view Wi-Fi state</string>
+  <string name="permlab_addSystemService">publish low-level services</string>
+  <string name="permlab_batteryStats">modify battery statistics</string>
+  <string name="permlab_bluetooth">create Bluetooth connections</string>
+  <string name="permlab_bluetoothAdmin">bluetooth administration</string>
+  <string name="permlab_brick">permanently disable phone</string>
+  <string name="permlab_broadcastPackageRemoved">send package removed broadcast</string>
+  <string name="permlab_broadcastSticky">send sticky broadcast</string>
+  <string name="permlab_callPhone">directly call phone numbers</string>
+  <string name="permlab_camera">take pictures</string>
+  <string name="permlab_changeComponentState">enable or disable application components</string>
+  <string name="permlab_changeConfiguration">change your UI settings</string>
+  <string name="permlab_changeNetworkState">change network connectivity</string>
+  <string name="permlab_changeWifiState">change Wi-Fi state</string>
+  <string name="permlab_clearAppCache">delete all application cache data</string>
+  <string name="permlab_clearAppUserData">delete other application's data</string>
+  <string name="permlab_createNetworkSockets">full Internet access</string>
+  <string name="permlab_deleteCacheFiles">delete other application's cache</string>
+  <string name="permlab_deletePackages">delete applications</string>
+  <string name="permlab_devicePower">power phone on or off</string>
+  <string name="permlab_disableKeyguard">disable keylock</string>
+  <string name="permlab_dump">retrieve system internal state</string>
+  <string name="permlab_expandStatusBar">expand/collapse status bar</string>
+  <string name="permlab_factoryTest">run in factory test mode</string>
+  <string name="permlab_flashlight">control flashlight</string>
+  <string name="permlab_forceBack">force application to close</string>
+  <string name="permlab_fotaUpdate">automatically install system updates</string>
+  <string name="permlab_getAccounts">discover known accounts</string>
+  <string name="permlab_getPackageSize">measure application storage space</string>
+  <string name="permlab_getTasks">retrieve running applications</string>
+  <string name="permlab_hardware_test">test hardware</string>
+  <string name="permlab_injectEvents">press keys and control buttons</string>
+  <string name="permlab_installPackages">directly install applications</string>
+  <string name="permlab_internalSystemWindow">display unauthorized windows</string>
+  <string name="permlab_manageAppTokens">manage application tokens</string>
+  <string name="permlab_masterClear">reset system to factory defaults</string>
+  <string name="permlab_modifyAudioSettings">change your audio settings</string>
+  <string name="permlab_modifyPhoneState">modify phone state</string>
+  <string name="permlab_mount_unmount_filesystems">mount and unmount filesystems</string>
+  <string name="permlab_persistentActivity">make application always run</string>
+  <string name="permlab_processOutgoingCalls">intercept outgoing calls</string>
+  <string name="permlab_readCalendar">read calendar data</string>
+  <string name="permlab_readContacts">read contact data</string>
+  <string name="permlab_readFrameBuffer">read frame buffer</string>
+  <string name="permlab_readInputState">record what you type and actions you take</string>
+  <string name="permlab_readLogs">read system log files</string>
+  <string name="permlab_readOwnerData">read owner data</string>
+  <string name="permlab_readPhoneState">read phone state</string>
+  <string name="permlab_readSms">read SMS or MMS</string>
+  <string name="permlab_readSyncSettings">read sync settings</string>
+  <string name="permlab_readSyncStats">read sync statistics</string>
+  <string name="permlab_receiveBootCompleted">automatically start at boot</string>
+  <string name="permlab_receiveMms">receive MMS</string>
+  <string name="permlab_receiveSms">receive SMS</string>
+  <string name="permlab_receiveWapPush">receive WAP</string>
+  <string name="permlab_recordAudio">record audio</string>
+  <string name="permlab_reorderTasks">reorder running applications</string>
+  <string name="permlab_restartPackages">restart other applications</string>
+  <string name="permlab_runSetActivityWatcher">monitor and control all application launching</string>
+  <string name="permlab_sendSms">send SMS messages</string>
+  <string name="permlab_setAlwaysFinish">make all background applications close</string>
+  <string name="permlab_setAnimationScale">modify global animation speed</string>
+  <string name="permlab_setDebugApp">enable application debugging</string>
+  <string name="permlab_setOrientation">change screen orientation</string>
+  <string name="permlab_setPreferredApplications">set preferred applications</string>
+  <string name="permlab_setProcessForeground">keep from being stopped</string>
+  <string name="permlab_setProcessLimit">limit number of running processes</string>
+  <string name="permlab_setTimeZone">set time zone</string>
+  <string name="permlab_setWallpaper">set wallpaper</string>
+  <string name="permlab_setWallpaperHints">set wallpaper size hints</string>
+  <string name="permlab_signalPersistentProcesses">send Linux signals to applications</string>
+  <string name="permlab_statusBar">disable or modify status bar</string>
+  <string name="permlab_systemAlertWindow">display system-level alerts</string>
+  <string name="permlab_vibrate">control vibrator</string>
+  <string name="permlab_wakeLock">prevent phone from sleeping</string>
+  <string name="permlab_writeCalendar">write calendar data</string>
+  <string name="permlab_writeContacts">write contact data</string>
+  <string name="permlab_writeOwnerData">write owner data</string>
+  <string name="permlab_writeSettings">modify global system settings</string>
+  <string name="permlab_writeSms">edit SMS or MMS</string>
+  <string name="permlab_writeSyncSettings">write sync settings</string>
+  <string name="petabyteShort">PB</string>
+  <string name="pm">"PM"</string>
+  <string name="power_dialog">Phone options</string>
+  <string name="power_off">Power off</string>
+  <string name="prepend_shortcut_label">Menu+</string>
+  <string name="preposition_for_date">on %s</string>
+  <string name="preposition_for_time">at %s</string>
+  <string name="preposition_for_year">in %s</string>
+  <string name="ringtone_default">Default ringtone</string>
+  <string name="ringtone_default_with_actual">Default ringtone (<xliff:g id="actual_ringtone">%1$s</xliff:g>)</string>
+  <string name="ringtone_picker_title">Select a ringtone</string>
+  <string name="ringtone_silent">Silent</string>
+  <string name="ringtone_unknown">Unknown ringtone</string>
+  <string name="safeMode">Safe mode</string>
+  <string name="same_month_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s</xliff:g>"</string>
+  <string name="same_month_md1_time1_md2_time2">"<xliff:g id="format">%2$s %3$s, %5$s \u2013 %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_month_mdy1_mdy2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s, %9$s</xliff:g>"</string>
+  <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s %3$s, %4$s, %5$s \u2013 %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %5$s \u2013 %6$s, %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s, %5$s \u2013 %6$s, %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s \u2013 %6$s, %7$s %8$s, %9$s</xliff:g>"</string>
+  <string name="same_year_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %7$s %8$s</xliff:g>"</string>
+  <string name="same_year_md1_time1_md2_time2">"<xliff:g id="format">%2$s %3$s, %5$s \u2013 %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_year_mdy1_mdy2">"<xliff:g id="format">%2$s %3$s \u2013 %7$s %8$s, %9$s</xliff:g>"</string>
+  <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s %3$s, %4$s, %5$s \u2013 %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %5$s \u2013 %6$s, %7$s %8$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s, %5$s \u2013 %6$s, %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s, %9$s</xliff:g>"</string>
+  <string name="saturday">Saturday</string>
+  <string name="save_password_label">Confirm</string>
+  <string name="save_password_message">Do you want the browser to remember this password?</string>
+  <string name="save_password_never">Never</string>
+  <string name="save_password_notnow">Not now</string>
+  <string name="save_password_remember">Remember</string>
+  <string name="screen_lock">Screen lock</string>
+  <string name="screen_progress">Working\u2026</string>
+  <string name="search_go">Search</string>
+  <string name="second">sec</string>
+  <string name="seconds">secs</string>
+  <string name="selectAll">Select all</string>
+  <string name="selectMenuLabel">Select</string>
+  <string name="select_character">Select character to insert</string>
+  <string name="sendText">Select an action for text</string>
+  <string name="serviceClassData">Data</string>
+  <string name="serviceClassDataAsync">Async</string>
+  <string name="serviceClassDataSync">Sync</string>
+  <string name="serviceClassFAX">FAX</string>
+  <string name="serviceClassPAD">PAD</string>
+  <string name="serviceClassPacket">Packet</string>
+  <string name="serviceClassSMS">SMS</string>
+  <string name="serviceClassVoice">Voice</string>
+  <string name="serviceDisabled">Service has been disabled.</string>
+  <string name="serviceEnabled">Service was enabled.</string>
+  <string name="serviceEnabledFor">Service was enabled for:</string>
+  <string name="serviceErased">Erasure was successful.</string>
+  <string name="serviceNotProvisioned">Service not provisioned.</string>
+  <string name="serviceRegistered">Registration was successful.</string>
+  <string name="shutdown_confirm">Your phone will shut down.</string>
+  <string name="shutdown_progress">Shutting down\u2026</string>
+  <string name="silent_mode">Silent mode</string>
+  <string name="simAbsentLabel">SIM card absent or incorrectly inserted.</string>
+  <string name="simNetworkPersonalizationLabel">SIM card cannot be used on this phone.</string>
+  <string name="simPINLabel">SIM PIN required (and presently unsupported).</string>
+  <string name="simPUKLabel">SIM PUK required (and presently unsupported).</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_no">Cancel</string>
+  <string name="sms_control_title">Sending SMS messages</string>
+  <string name="sms_control_yes">OK</string>
+  <string name="status_bar_applications_title">Application</string>
+  <string name="status_bar_clear_all_button">Clear notifications</string>
+  <string name="status_bar_date_format">"<xliff:g id="format">d, MMMM, yyyy</xliff:g>"</string>
+  <string name="status_bar_latest_events_title">Notifications</string>
+  <string name="status_bar_no_notifications_title">No notifications</string>
+  <string name="status_bar_ongoing_events_title">Ongoing</string>
+  <string name="status_bar_time_format">"<xliff:g id="format">h:mm AA</xliff:g>"</string>
+  <string name="sunday">Sunday</string>
+  <string name="terabyteShort">TB</string>
+  <string name="text_copied">Text copied to clipboard.</string>
+  <string name="thursday">Thursday</string>
+  <string name="time1_time2">"<xliff:g id="format">%1$s \u2013 %2$s</xliff:g>"</string>
+  <string name="time_date">"<xliff:g id="format">%1$s, %3$s</xliff:g>"</string>
+  <string name="time_picker_set">Set</string>
+  <string name="time_wday">"<xliff:g id="format">%1$s, %2$s</xliff:g>"</string>
+  <string name="time_wday_date">"<xliff:g id="format">%1$s, %2$s, %3$s</xliff:g>"</string>
+  <string name="today">Today</string>
+  <string name="tomorrow">Tomorrow</string>
+  <string name="tuesday">Tuesday</string>
+  <string name="turn_off_radio">Turn off wireless</string>
+  <string name="turn_on_radio">Turn on wireless</string>
+  <string name="unknownName">(Unknown)</string>
+  <string name="untitled">&lt;untitled&gt;</string>
+  <string name="volume_alarm">Alarm volume</string>
+  <string name="volume_call">In-call volume</string>
+  <string name="volume_music">Music/video volume</string>
+  <string name="volume_ringtone">Ringer volume</string>
+  <string name="volume_unknown">Volume</string>
+  <string name="wait">Wait</string>
+  <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="format">%1$s, %2$s, %3$s \u2013 %4$s, %5$s, %6$s</xliff:g>"</string>
+  <string name="wday1_date1_wday2_date2">"<xliff:g id="format">%1$s, %2$s \u2013 %4$s, %5$s</xliff:g>"</string>
+  <string name="wday_date">"<xliff:g id="format">%2$s, %3$s</xliff:g>"</string>
+  <string name="web_user_agent"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android 0.6; %s)
+        AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2</xliff:g></string>
+  <string name="wednesday">Wednesday</string>
+  <string name="week">week</string>
+  <string name="weekly">"Weekly on <xliff:g id="day">%s</xliff:g>"</string>
+  <string name="weekly_format">d MMM</string>
+  <string name="weeks">weeks</string>
+  <string name="whichApplication">Complete action using</string>
+  <string name="year">year</string>
+  <string name="yearly">Yearly</string>
+  <string name="yearly_format">yyyy</string>
+  <string name="years">years</string>
+  <string name="yes">OK</string>
+  <string name="yesterday">Yesterday</string>
+</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..9e165d9
--- /dev/null
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -0,0 +1,574 @@
+<?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="BaMmi">Bloqueo de llamadas</string>
+  <string name="CLIRDefaultOffNextCallOff">El valor predeterminado de la restricción de ID es no restringida. Próxima llamada: no restringida</string>
+  <string name="CLIRDefaultOffNextCallOn">El valor predeterminado de la restricción de ID es no restringida. Próxima llamada: restringida</string>
+  <string name="CLIRDefaultOnNextCallOff">El valor predeterminado de la restricción de ID es restringida. Próxima llamada: no restringida</string>
+  <string name="CLIRDefaultOnNextCallOn">El valor predeterminado de la restricción de ID es restringida. Próxima llamada: restringida</string>
+  <string name="CLIRPermanent">Restricción de ID prevista en el modo permanente.</string>
+  <string name="CfMmi">Desvío de llamadas</string>
+  <string name="ClipMmi">ID de llamada entrante</string>
+  <string name="ClirMmi">ID de llamada saliente</string>
+  <string name="CwMmi">Llamada en espera</string>
+  <string name="Midnight">"Media noche"</string>
+  <string name="Noon">"Mediodía"</string>
+  <string name="PinMmi">Cambio de PIN</string>
+  <string name="PwdMmi">Cambio de contraseña</string>
+  <string name="VideoView_error_button">Aceptar</string>
+  <string name="VideoView_error_text_unknown">Se ha producido un error al reproducir el vídeo seleccionado.</string>
+  <string name="VideoView_error_title">Error de reproducción del vídeo</string>
+  <string name="abbrev_month">"<xliff:g id="format">%b</xliff:g>"</string>
+  <string name="abbrev_month_day">"<xliff:g id="format">%-d %b</xliff:g>"</string>
+  <string name="abbrev_month_day_year">"<xliff:g id="format">%-d %b, %Y</xliff:g>"</string>
+  <string name="abbrev_month_year">"<xliff:g id="format">%b %Y</xliff:g>"</string>
+  <string name="activate_keyguard">Activar protección de clave</string>
+  <string name="ago">hace</string>
+  <string name="alwaysUse">Usar siempre esta aplicación para esta actividad</string>
+  <string name="am">"AM"</string>
+  <string name="battery_low_percent_format">Menos de <xliff:g id="number">%d%%</xliff:g>
+    restantes</string>
+  <string name="battery_low_subtitle">Batería baja</string>
+  <string name="battery_low_title">Conectar cargador</string>
+  <string name="battery_status_charging">Cargando\u2026</string>
+  <string name="battery_status_text_percent_format"><xliff:g id="number">%d%%</xliff:g></string>
+  <string name="before">Antes</string>
+  <string name="browserSavedFormData">Datos de formulario guardados</string>
+  <string name="byteShort">b</string>
+  <string name="cancel">Cancelar</string>
+  <string name="capital_off">Desactivar</string>
+  <string name="capital_on">Activar</string>
+  <string name="cfReasonBusy">Desvío de llamadas ocupado</string>
+  <string name="cfReasonNR">Desvío de llamadas no accesible</string>
+  <string name="cfReasonNRy">Desvío de llamadas sin respuesta</string>
+  <string name="cfReasonUnconditional">Desvío de llamadas incondicional</string>
+  <string name="cfTemplateForwarded">{0}: {1}</string>
+  <string name="cfTemplateForwardedTime">{0}: {1} después de {2} segundos</string>
+  <string name="cfTemplateNotForwarded">{0}: No desviada</string>
+  <string name="cfTemplateRegistered">{0}: No desviada ({1})</string>
+  <string name="cfTemplateRegisteredTime">{0}: No desviada ({1} después de {2} segundos)</string>
+  <string name="chooseActivity">Seleccionar una acción</string>
+  <string name="contentServiceSync">Sincronizar</string>
+  <string name="contentServiceSyncNotificationDesc">Sincronizando</string>
+  <string name="contentServiceSyncNotificationTitle">Sincronizar</string>
+  <string name="contentServiceXmppAvailable">XMPP activo</string>
+  <string name="copy">Copiar</string>
+  <string name="copyUrl">Copiar URL</string>
+  <string name="cut">Cortar</string>
+  <string name="daily">Diario</string>
+  <string name="daily_format">h:mm aa</string>
+  <string name="date1_date2">"<xliff:g id="format">%2$s \u2013 %5$s</xliff:g>"</string>
+  <string name="date1_time1_date2_time2">"<xliff:g id="format">%2$s, %3$s \u2013 %5$s, %6$s</xliff:g>"</string>
+  <string name="date_picker_set">Establecer</string>
+  <string name="date_range_separator">" \u2013 "</string>
+  <string name="day">día</string>
+  <string name="day_of_week_long_friday">Viernes</string>
+  <string name="day_of_week_long_monday">Lunes</string>
+  <string name="day_of_week_long_saturday">Sábado</string>
+  <string name="day_of_week_long_sunday">Domingo</string>
+  <string name="day_of_week_long_thursday">Jueves</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_medium_friday">Vie</string>
+  <string name="day_of_week_medium_monday">Lun</string>
+  <string name="day_of_week_medium_saturday">Sáb</string>
+  <string name="day_of_week_medium_sunday">Dom</string>
+  <string name="day_of_week_medium_thursday">Jue</string>
+  <string name="day_of_week_medium_tuesday">Mar</string>
+  <string name="day_of_week_medium_wednesday">Mié</string>
+  <string name="day_of_week_short_friday">Vi</string>
+  <string name="day_of_week_short_monday">Lu</string>
+  <string name="day_of_week_short_saturday">Sá</string>
+  <string name="day_of_week_short_sunday">Do</string>
+  <string name="day_of_week_short_thursday">Ju</string>
+  <string name="day_of_week_short_tuesday">Ma</string>
+  <string name="day_of_week_short_wednesday">Mi</string>
+  <string name="day_of_week_shorter_friday">V</string>
+  <string name="day_of_week_shorter_monday">L</string>
+  <string name="day_of_week_shorter_saturday">S</string>
+  <string name="day_of_week_shorter_sunday">D</string>
+  <string name="day_of_week_shorter_thursday">J</string>
+  <string name="day_of_week_shorter_tuesday">M</string>
+  <string name="day_of_week_shorter_wednesday">X</string>
+  <string name="day_of_week_shortest_friday">V</string>
+  <string name="day_of_week_shortest_monday">L</string>
+  <string name="day_of_week_shortest_saturday">S</string>
+  <string name="day_of_week_shortest_sunday">D</string>
+  <string name="day_of_week_shortest_thursday">J</string>
+  <string name="day_of_week_shortest_tuesday">M</string>
+  <string name="day_of_week_shortest_wednesday">X</string>
+  <string name="days">días</string>
+  <string name="daysDurationFuturePlural">en <xliff:g id="days">%d</xliff:g> días</string>
+  <string name="daysDurationPastPlural">Hace <xliff:g id="days">%d</xliff:g> días</string>
+  <string name="defaultMsisdnAlphaTag">Msisdn1</string>
+  <string name="defaultVoiceMailAlphaTag">Correo de voz</string>
+  <string name="elapsed_time_short_format_h_mm_ss"><xliff:g id="format">%1$d:%2$02d:%3$02d</xliff:g></string>
+  <string name="elapsed_time_short_format_mm_ss"><xliff:g id="format">%1$02d:%2$02d</xliff:g></string>
+  <string name="ellipsis">\u2026</string>
+  <string name="emergency_call_dialog_call">Llamada de emergencia</string>
+  <string name="emergency_call_dialog_cancel">Cancelar</string>
+  <string name="emergency_call_dialog_number_for_display">Número de emergencia</string>
+  <string name="emergency_call_dialog_text">¿Realizar una llamada de emergencia?</string>
+  <string name="emergency_call_number_uri">tel:112</string>
+  <string name="emptyPhoneNumber">(ningún número de teléfono)</string>
+  <string name="every_weekday">"Todos los días laborables (Lun\u2013Vie)"</string>
+  <string name="factorytest_failed">Fallo al realizar la prueba de fábrica</string>
+  <string name="factorytest_no_action">No se ha encontrado ningún paquete que proporcione la
+        acción FACTORY_TEST.</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_reboot">Reiniciar</string>
+  <string name="friday">Viernes</string>
+  <string name="gigabyteShort">Gb</string>
+  <string name="global_action_lock">Bloquear</string>
+  <string name="global_action_power_off">Apagado</string>
+  <string name="global_action_silent_mode_off_status">El sonido está activado</string>
+  <string name="global_action_silent_mode_on_status">El sonido está desactivado</string>
+  <string name="global_action_toggle_silent_mode">Modo silencioso</string>
+  <string name="global_actions">Acciones globales</string>
+  <string name="hour">hora</string>
+  <string name="hours">horas</string>
+  <string name="httpError">Error desconocido</string>
+  <string name="httpErrorAuth">Error de autenticación</string>
+  <string name="httpErrorBadUrl">URL no válida</string>
+  <string name="httpErrorConnect">Fallo al conectar con el servidor</string>
+  <string name="httpErrorFailedSslHandshake">Fallo al realizar SSL mutuo</string>
+  <string name="httpErrorFile">Error en el archivo</string>
+  <string name="httpErrorFileNotFound">Archivo no encontrado</string>
+  <string name="httpErrorIO">Fallo al leer o escribir en el servidor</string>
+  <string name="httpErrorLookup">Host desconocido</string>
+  <string name="httpErrorOk">Aceptar</string>
+  <string name="httpErrorProxyAuth">Error de autenticación de servidor proxy</string>
+  <string name="httpErrorRedirectLoop">Demasiadas redirecciones de servidor</string>
+  <string name="httpErrorTimeout">Agotado el tiempo de espera de conexión con el servidor</string>
+  <string name="httpErrorUnsupportedAuthScheme">Esquema de autenticación no compatible. Fallo al autenticar.</string>
+  <string name="httpErrorUnsupportedScheme">Protocolo no compatible</string>
+  <string name="in">en</string>
+  <string name="keyguard_label_text">Para desbloquear, pulse Menú y luego 0.</string>
+  <string name="keyguard_password_emergency_instructions">Pulse la tecla Llamar para realizar la llamada de emergencia.</string>
+  <string name="keyguard_password_enter_pin_code">Introducir código PIN</string>
+  <string name="keyguard_password_instructions">Introduzca el código de acceso o marque un número de emergencia.</string>
+  <string name="keyguard_password_wrong_pin_code">¡Código PIN incorrecto!</string>
+  <string name="kilobyteShort">Kb</string>
+  <string name="lockscreen_carrier_default">(Fuera de servicio)</string>
+  <string name="lockscreen_carrier_key">gsm.operator.alpha</string>
+  <string name="lockscreen_emergency_call">Llamada de emergencia</string>
+  <string name="lockscreen_instructions_when_pattern_disabled">Pulse Menú para desbloquear</string>
+  <string name="lockscreen_instructions_when_pattern_enabled">Pulse Menú para desbloquear o realice una llamada de emergencia</string>
+  <string name="lockscreen_low_battery">Conectar cargador</string>
+  <string name="lockscreen_missing_sim_instructions">Inserte una tarjeta SIM</string>
+  <string name="lockscreen_missing_sim_message">No hay ninguna tarjeta SIM en el dispositivo</string>
+  <string name="lockscreen_pattern_correct">¡Correcto!</string>
+  <string name="lockscreen_pattern_instructions">Trazar patrón de desbloqueo</string>
+  <string name="lockscreen_pattern_wrong">¡Patrón incorrecto! Inténtelo de nuevo</string>
+  <string name="lockscreen_plugged_in">Cargando (<xliff:g id="number">%d%%</xliff:g>)</string>
+  <string name="lockscreen_screen_locked">Pantalla bloqueada</string>
+  <string name="lockscreen_too_many_failed_attempts_countdown">Inténtelo de nuevo en <xliff:g id="number">%d</xliff:g>  segundos</string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_message">
+        Tiene <xliff:g id="number">%d</xliff:g> intentos fallidos para
+        trazar correctamente su patrón de desbloqueo.\n
+        Inténtelo de nuevo en <xliff:g id="number">%d</xliff:g> segundos.
+    </string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_title">Advertencia de patrón de bloqueo</string>
+  <string name="low_internal_storage_text">Espacio de almacenamiento interno bajo</string>
+  <string name="low_internal_storage_view_text">El dispositivo se está quedando sin espacio de almacenamiento interno</string>
+  <string name="low_internal_storage_view_title">Espacio de almacenamiento interno bajo</string>
+  <string name="megabyteShort">Mb</string>
+  <string name="midnight">"media noche"</string>
+  <string name="minute">minuto</string>
+  <string name="minutes">minutos</string>
+  <string name="mmiComplete">MMI completo</string>
+  <string name="mmiError">Error de red o código MMI no válido.</string>
+  <string name="monday">Lunes</string>
+  <string name="month">"<xliff:g id="format">%B</xliff:g>"</string>
+  <string name="month_day">"<xliff:g id="format">%-d %B</xliff:g>"</string>
+  <string name="month_day_year">"<xliff:g id="format">%-d %B, %Y</xliff:g>"</string>
+  <string name="month_long_april">Abril</string>
+  <string name="month_long_august">Agosto</string>
+  <string name="month_long_december">Diciembre</string>
+  <string name="month_long_february">Febrero</string>
+  <string name="month_long_january">Enero</string>
+  <string name="month_long_july">Julio</string>
+  <string name="month_long_june">Junio</string>
+  <string name="month_long_march">Marzo</string>
+  <string name="month_long_may">Mayo</string>
+  <string name="month_long_november">Noviembre</string>
+  <string name="month_long_october">Octubre</string>
+  <string name="month_long_september">Septiembre</string>
+  <string name="month_medium_april">Abr</string>
+  <string name="month_medium_august">Ago</string>
+  <string name="month_medium_december">Dic</string>
+  <string name="month_medium_february">Feb</string>
+  <string name="month_medium_january">Ene</string>
+  <string name="month_medium_july">Jul</string>
+  <string name="month_medium_june">Jun</string>
+  <string name="month_medium_march">Mar</string>
+  <string name="month_medium_may">May</string>
+  <string name="month_medium_november">Nov</string>
+  <string name="month_medium_october">Oct</string>
+  <string name="month_medium_september">Sep</string>
+  <string name="month_shortest_april">A</string>
+  <string name="month_shortest_august">A</string>
+  <string name="month_shortest_december">D</string>
+  <string name="month_shortest_february">F</string>
+  <string name="month_shortest_january">E</string>
+  <string name="month_shortest_july">E</string>
+  <string name="month_shortest_june">E</string>
+  <string name="month_shortest_march">M</string>
+  <string name="month_shortest_may">M</string>
+  <string name="month_shortest_november">N</string>
+  <string name="month_shortest_october">O</string>
+  <string name="month_shortest_september">S</string>
+  <string name="month_year">"<xliff:g id="format">%B %Y</xliff:g>"</string>
+  <string name="monthly">Mensual</string>
+  <string name="monthly_format">d MMM</string>
+  <string name="more_item_label">Más</string>
+  <string name="no">Cancelar</string>
+  <string name="noApplications">No hay ninguna aplicación disponible para llevar a cabo
+        la acción</string>
+  <string name="no_recent_tasks">Ninguna aplicación reciente</string>
+  <string name="noon">"mediodía"</string>
+  <string name="numeric_date">"<xliff:g id="format">%d/%m/%Y</xliff:g>"</string>
+  <string name="numeric_date_notation">"<xliff:g id="format">%d/%m/%Y</xliff:g>"</string>
+  <string name="numeric_md1_md2">"<xliff:g id="format">%3$s/%2$s \u2013 %8$s/%7$s</xliff:g>"</string>
+  <string name="numeric_md1_time1_md2_time2">"<xliff:g id="format">%3$s/%2$s, %5$s \u2013 %8$s/%7$s, %10$s</xliff:g>"</string>
+  <string name="numeric_mdy1_mdy2">"<xliff:g id="format">%3$s/%2$s/%4$s \u2013 %8$s/%7$s/%9$s</xliff:g>"</string>
+  <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s/%2$s/%4$s, %5$s \u2013 %8$s/%7$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %3$s/%2$s, %5$s \u2013 %6$s, %8$s/%7$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %3$s/%2$s \u2013 %6$s, %8$s/%7$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %3$s/%2$s/%4$s, %5$s \u2013 %6$s, %8$s/%7$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %3$s/%2$s/%4$s \u2013 %6$s, %8$s/%7$s/%9$s</xliff:g>"</string>
+  <string name="ok">Aceptar</string>
+  <string name="open_permission_deny">No tiene permiso para abrir esta página.</string>
+  <string name="passwordIncorrect">Contraseña incorrecta</string>
+  <string name="paste">Pegar</string>
+  <string name="permdesc_accessFineLocation">Acceda al sistema de posicionamiento global (GPS) del
+        dispositivo, en caso de que esté disponible.
+        Una aplicación maliciosa puede usar esta función para determinar dónde está y puede
+        consumir batería adicional.</string>
+  <string name="permdesc_accessCoarseLocation">Utilice la base de datos de la red para determinar una
+        ubicación aproximada del dispositivo, en caso de que esté disponible. Una aplicación maliciosa puede usar
+        esta opción para determinar aproximadamente dónde está.</string>
+  <string name="permdesc_accessPhoneInterface">Permite que la aplicación acceda
+        a la interfaz interna del teléfono. Esta función no debería ser necesaria para las aplicaciones normales.</string>
+  <string name="permdesc_accessSurfaceFlinger">Permite que la aplicación use
+        las características de bajo nivel de SurfaceFlinger.</string>
+  <string name="permdesc_addSystemService">Permite que la aplicación publique
+        sus propios servicios de sistema de bajo nivel. Una aplicación maliciosa puede apropiarse del sistema y robar
+        o dañar cualquier dato.</string>
+  <string name="permdesc_brick">Permite que la aplicación deshabilite
+        de forma permanente todo el dispositivo. Esto es muy peligroso.</string>
+  <string name="permdesc_broadcastPackageRemoved">Permite que una aplicación
+        emita una notificación de que se ha eliminado un paquete de la aplicación.
+        Una aplicación maliciosa puede usar esta función para acabar con cualquier otra
+        aplicación que se esté ejecutando.</string>
+  <string name="permdesc_broadcastSticky">Permite que una aplicación envíe
+        emisiones continuas, que permanecen una vez que termina la emisión.
+        Una aplicación maliciosa puede hacer que el dispositivo sea lento o inestable y
+        use demasiada memoria.</string>
+  <string name="permdesc_callPhone">Permite que la aplicación llame
+        a los números de teléfono sin que usted intervenga. Una aplicación maliciosa puede
+        hacer que aparezcan llamadas inesperadas en su factura del teléfono.</string>
+  <string name="permdesc_camera">Permite que la aplicación saque fotos
+        con la cámara. Esto permite que la aplicación capture en cualquier momento
+        imágenes que la cámara esté viendo.</string>
+  <string name="permdesc_changeComponentState">Permite que una aplicación cambie si un
+        componente de otra aplicación está habilitado o no. Una aplicación maliciosa puede usar esta función
+        para deshabilitar importantes capacidades del dispositivo. Se debe tener cuidado con el permiso, ya que
+        los componentes de la aplicación se pueden volver inestables o inconsistentes.
+    </string>
+  <string name="permdesc_changeConfiguration">Permite que una aplicación
+        cambie la configuración actual, como la hora local o el tamaño general de la
+        fuente.</string>
+  <string name="permdesc_clearAppCache">Permite que una aplicación libere memoria de almacenamiento
+        del dispositivo eliminando archivos del directorio caché de la aplicación. El acceso suele estar 
+        muy restringido al proceso del sistema.</string>
+  <string name="permdesc_deleteCacheFiles">Permite que una aplicación elimine
+        archivos de la caché.</string>
+  <string name="permdesc_deletePackages">Permite que una aplicación elimine
+        paquetes Android. Una aplicación maliciosa puede usar esta función para eliminar importantes aplicaciones.</string>
+  <string name="permdesc_devicePower">Permite a la aplicación encender o
+        apagar el dispositivo o mantenerlo encendido.</string>
+  <string name="permdesc_dump">Permite a la aplicación recuperar
+        el estado interno del sistema. Una aplicación maliciosa puede recuperar
+        una gran variedad de información privada y segura que normalmente
+        no debería necesitar nunca.</string>
+  <string name="permdesc_factoryTest">Se ejecuta como una prueba de fabricante de bajo nivel,
+        permitiendo el acceso completo al hardware del dispositivo. Sólo disponible
+        cuando un dispositivo se ejecuta en el modo de prueba del fabricante.</string>
+  <string name="permdesc_flashlight">Permite a la aplicación controlar
+        la linterna.</string>
+  <string name="permdesc_forceBack">Permite a la aplicación hacer que cualquier
+        actividad que esté en primer plano se cierre y pase a segundo plano.
+        Esta función no debería ser necesaria para las aplicaciones normales.</string>
+  <string name="permdesc_fotaUpdate">Permite a una aplicación recibir
+        notifications about pending system updates and trigger their
+        su instalación. Una aplicación maliciosa puede usar esta función para corromper el sistema
+        con actualizaciones no autorizadas o interferir en el proceso
+        de actualización.</string>
+  <string name="permdesc_getTasks">Permite a la aplicación recuperar
+        información sobre tareas que se acaban de ejecutar y tareas que se están ejecutando actualmente. Puede permitir que
+        una aplicación maliciosa
+        descubra información privada sobre otras aplicaciones.</string>
+  <string name="permdesc_hardware_test">Permite a la aplicación controlar
+        distintos periféricos para realizar una prueba de hardware.</string>
+  <string name="permdesc_injectEvents">Permite a una aplicación ofrecer
+        sus propios eventos de entrada (pulsaciones de teclas, etc.) a otras aplicaciones. Una aplicación
+        maliciosa puede usar esta función para controlar el dispositivo.</string>
+  <string name="permdesc_installPackages">Permite a una aplicación instalar paquetes Android
+        nuevos o actualizados. Una aplicación maliciosa puede usar esta función para agregar nuevas aplicaciones con
+        permisos arbitrariamente poderosos.</string>
+  <string name="permdesc_internalSystemWindow">Permite la creación de
+        ventanas destinadas a ser usadas por la interfaz de usuario
+        del sistema interno. No está destinada al uso por aplicaciones normales.</string>
+  <string name="permdesc_manageAppTokens">Permite a las aplicaciones
+        crear y gestionar sus propios credenciales, saltándose su orden Z
+        normal. Esta función no debería ser necesaria para las aplicaciones normales.</string>
+  <string name="permdesc_masterClear">Permite que una aplicación restablezca
+        por completo los valores de fábrica del sistema, borrando todos los datos,
+        la configuración y las aplicaciones instaladas.</string>
+  <string name="permdesc_modifyAudioSettings">Permite que la aplicación modifique
+        la configuración de audio global, como el volumen y el enrutamiento.</string>
+  <string name="permdesc_mount_unmount_filesystems">Permite que la aplicación monte y
+        desmonte archivos del sistema para el almacenamiento extraíble.</string>
+  <string name="permdesc_persistentActivity">Permite que una aplicación convierta
+        partes de sí misma en persistentes, de forma que el sistema no pueda usarlas
+        para otras aplicaciones.</string>
+  <string name="permdesc_raisedThreadPriority">Permite que la aplicación use
+        prioridades de cadena elevadas, lo que puede afectar a la capacidad de respuesta
+        de la interfaz de usuario.</string>
+  <string name="permdesc_readContacts">Permite que una aplicación lea todos
+        los datos (de dirección) de contacto almacenados en su dispositivo. Una aplicación maliciosa
+        puede usar esta función para enviar sus datos a otras personas.</string>
+  <string name="permdesc_readFrameBuffer">Permite que la aplicación
+        lea el contenido del búfer de trama.</string>
+  <string name="permdesc_readInputState">Permite que las aplicaciones vigilen las
+        teclas que pulsa incluso cuando interactúe con otra aplicación (como
+        al introducir una contraseña). Esta función no debería ser necesaria para las aplicaciones normales.</string>
+  <string name="permdesc_readSms">Permite que la aplicación lea
+      los mensajes SMS almacenados en su teléfono o tarjeta SIM. Una aplicación maliciosa
+      puede leer sus mensajes confidenciales.</string>
+  <string name="permdesc_receiveBootCompleted">Permite que una aplicación
+        se inicie en cuanto el sistema haya terminado de arrancar.
+        Esto puede hacer que el dispositivo tarde más en iniciarse y que
+        la aplicación ralentice todo el dispositivo al ejecutarse siempre.</string>
+  <string name="permdesc_receiveMms">Permite que la aplicación reciba
+      y procese mensajes multimedia. Una aplicación maliciosa puede controlar
+      sus mensajes o eliminarlos sin que usted los vea.</string>
+  <string name="permdesc_receiveSms">Permite que la aplicación reciba
+      y procese mensajes de texto. Una aplicación maliciosa puede controlar
+      sus mensajes o eliminarlos sin que usted los vea.</string>
+  <string name="permdesc_receiveWapPush">Permite que la aplicación reciba
+      y procese mensajes WAP. Una aplicación maliciosa puede controlar
+      sus mensajes o eliminarlos sin que usted los vea.</string>
+  <string name="permdesc_recordAudio">Permite que la aplicación acceda
+        a la ruta de grabación de audio.</string>
+  <string name="permdesc_reorderTasks">Permite que una aplicación mueva
+        las tareas a primer plano y a segundo plano. Una aplicación maliciosa puede
+        aparecer en primer plano sin su control.</string>
+  <string name="permdesc_runInstrumentation">Permite que una aplicación
+        inserte su propio código de instrumentación en cualquier otra aplicación.
+        Una aplicación maliciosa puede comprometer todo el sistema. Este
+        permiso sólo es necesario para el desarrollo, nunca para el uso
+        normal del dispositivo.</string>
+  <string name="permdesc_runSetActivityWatcher">Permite que una aplicación
+        controle y supervise el modo en que el sistema inicia las actividades.
+        Una aplicación maliciosa puede comprometer todo el sistema. Este
+        permiso sólo es necesario para el desarrollo, nunca para el uso
+        normal del dispositivo.</string>
+  <string name="permdesc_sendSms">Permite a la aplicación enviar
+      mensajes de texto. Una aplicación maliciosa puede hacerle gastar dinero
+      enviando mensajes sin su confirmación.</string>
+  <string name="permdesc_setAlwaysFinish">Permite a una aplicación
+        controlar si las actividades han acabado en cuanto pasan
+        a segundo plano. Esta función
+        no es necesaria para las aplicaciones normales.</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="permdesc_setDebugApp">Permite a una aplicación activar
+        la depuración para otra aplicación. Una aplicación maliciosa puede usar esta función
+        para acabar con otras aplicaciones.</string>
+  <string name="permdesc_setOrientation">Permite que una aplicación cambie
+        la rotación de la pantalla en cualquier momento. No debería ser necesaria en
+        aplicaciones normales.</string>
+  <string name="permdesc_setPreferredApplications">Permite que una aplicación
+        modifique sus aplicaciones preferidas. Esta función puede permitir que una aplicación maliciosa
+        cambie silenciosamente las aplicaciones que se están ejecutando y
+        haga que las aplicaciones existentes recopilen datos privados.</string>
+  <string name="permdesc_setProcessLimit">Permite a una aplicación
+        controle el número máximo de procesos que se ejecutarán. Esta función
+        no es necesaria para las aplicaciones normales.</string>
+  <string name="permdesc_setWallpaper">Permite a la aplicación
+        establecer el fondo de escritorio del sistema.</string>
+  <string name="permdesc_signalPersistentProcesses">Permite a la aplicación solicitar que la señal
+        suministrada se envíe a todos los procesos persistentes.</string>
+  <string name="permdesc_statusBar">Permite a las aplicaciones
+        abrir, cerrar o deshabilitar la barra de estado y sus iconos.</string>
+  <string name="permdesc_systemAlertWindow">Permite a una aplicación
+        mostrar ventanas de alerta del sistema. Una aplicación maliciosa puede controlar toda la
+        pantalla del dispositivo.</string>
+  <string name="permdesc_vibrate">Permite a la aplicación controlar
+        el vibrador.</string>
+  <string name="permdesc_writeContacts">Permite a una aplicación modificar los
+        datos (de dirección) de contacto almacenados en su dispositivo. Una aplicación
+        maliciosa puede usar esta función para borrar o modificar sus datos de contacto.</string>
+  <string name="permdesc_writeSettings">Permite a una aplicación modificar los
+        datos de configuración del sistema. Una aplicación maliciosa puede corromper la configuración
+        del sistema.</string>
+  <string name="permdesc_writeSms">Permite a la aplicación escribir
+      mensajes SMS almacenados en su teléfono o tarjeta SIM. Una aplicación maliciosa
+      puede eliminar sus mensajes.</string>
+  <string name="permlab_accessFineLocation">Acceder a ubicación de GPS</string>
+  <string name="permlab_accessCoarseLocation">Acceder a ubicación de red</string>
+  <string name="permlab_accessPhoneInterface">Acceder a la interfaz del teléfono</string>
+  <string name="permlab_accessSurfaceFlinger">Acceder a SurfaceFlinger</string>
+  <string name="permlab_addSystemService">Agregar servicio del sistema</string>
+  <string name="permlab_brick">Deshabilitar dispositivo</string>
+  <string name="permlab_broadcastPackageRemoved">Paquete de emisión eliminado</string>
+  <string name="permlab_broadcastSticky">Intento de emisión permanente</string>
+  <string name="permlab_callPhone">Llamar a números de teléfono</string>
+  <string name="permlab_camera">Cámara</string>
+  <string name="permlab_changeComponentState">Habilitar o deshabilitar componentes de la aplicación</string>
+  <string name="permlab_changeConfiguration">Cambiar configuración</string>
+  <string name="permlab_clearAppCache">Borrar datos de la caché de la aplicación</string>
+  <string name="permlab_deleteCacheFiles">Borrar archivos de caché</string>
+  <string name="permlab_deletePackages">Borrar paquetes</string>
+  <string name="permlab_devicePower">Alimentación del dispositivo</string>
+  <string name="permlab_dump">Volcar estado del sistema</string>
+  <string name="permlab_factoryTest">Prueba de fábrica</string>
+  <string name="permlab_flashlight">Linterna</string>
+  <string name="permlab_forceBack">Restablecer</string>
+  <string name="permlab_fotaUpdate">Instalación de actualización del sistema</string>
+  <string name="permlab_getTasks">Obtener información de tareas</string>
+  <string name="permlab_hardware_test">Prueba de hardware</string>
+  <string name="permlab_injectEvents">Introducir eventos de entrada</string>
+  <string name="permlab_installPackages">Instalar paquetes</string>
+  <string name="permlab_internalSystemWindow">Ventana de sistema interno</string>
+  <string name="permlab_manageAppTokens">Administrar credenciales de aplicación</string>
+  <string name="permlab_masterClear">Restablecimiento de todo el sistema</string>
+  <string name="permlab_modifyAudioSettings">Modificar configuración de audio</string>
+  <string name="permlab_mount_unmount_filesystems">Montar y desmontar archivos del sistema</string>
+  <string name="permlab_persistentActivity">Actividades persistentes</string>
+  <string name="permlab_raisedThreadPriority">Prioridades de cadena elevadas</string>
+  <string name="permlab_readContacts">Leer datos de contacto</string>
+  <string name="permlab_readFrameBuffer">Leer búfer de trama</string>
+  <string name="permlab_readInputState">Leer estado de entrada</string>
+  <string name="permlab_readSms">Leer mensajes SMS/MMS</string>
+  <string name="permlab_receiveBootCompleted">Ejecutar al arrancar</string>
+  <string name="permlab_receiveMms">Recibir mensajes MMS</string>
+  <string name="permlab_receiveSms">Recibir mensajes SMS</string>
+  <string name="permlab_receiveWapPush">Recibir mensajes WAP</string>
+  <string name="permlab_recordAudio">Grabar audio</string>
+  <string name="permlab_reorderTasks">Reordenar tareas</string>
+  <string name="permlab_runInstrumentation">Ejecutar instrumentación</string>
+  <string name="permlab_runSetActivityWatcher">Establecer vigilante de actividades</string>
+  <string name="permlab_sendSms">Enviar mensajes SMS</string>
+  <string name="permlab_setAlwaysFinish">Establecer finalizar siempre</string>
+  <string name="permlab_setAnimationScale">Establecer escala de animación</string>
+  <string name="permlab_setDebugApp">Establecer aplicación de depuración</string>
+  <string name="permlab_setOrientation">Establecer orientación</string>
+  <string name="permlab_setPreferredApplications">Establecer aplicaciones preferidas</string>
+  <string name="permlab_setProcessLimit">Establecer límite de procesos</string>
+  <string name="permlab_setWallpaper">Establecer fondo de escritorio</string>
+  <string name="permlab_signalPersistentProcesses">Señalar procesos persistentes</string>
+  <string name="permlab_statusBar">Controlar la barra de estado</string>
+  <string name="permlab_systemAlertWindow">Ventana de alerta del sistema</string>
+  <string name="permlab_vibrate">Vibrador</string>
+  <string name="permlab_writeContacts">Escribir datos de contactos</string>
+  <string name="permlab_writeSettings">Escribir configuración del sistema</string>
+  <string name="permlab_writeSms">Escribir mensajes SMS/MMS</string>
+  <string name="petabyteShort">Pb</string>
+  <string name="pm">"PM"</string>
+  <string name="power_dialog">Opciones de energía</string>
+  <string name="power_off">Apagado</string>
+  <string name="prepend_shortcut_label">Menú+</string>
+  <string name="preposition_for_date">en %s</string>
+  <string name="preposition_for_time">en %s</string>
+  <string name="preposition_for_year">en %s</string>
+  <string name="safeMode">Modo seguro</string>
+  <string name="same_month_md1_md2">"<xliff:g id="format">%3$s \u2013 %8$s %2$s</xliff:g>"</string>
+  <string name="same_month_md1_time1_md2_time2">"<xliff:g id="format">%3$s %2$s, %5$s \u2013 %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_month_mdy1_mdy2">"<xliff:g id="format">%3$s \u2013 %8$s %2$s, %9$s</xliff:g>"</string>
+  <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s %2$s, %4$s, %5$s \u2013 %8$s %7$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %3$s %2$s, %5$s \u2013 %6$s, %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %3$s %2$s \u2013 %6$s, %8$s %7$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %3$s %2$s, %4$s, %5$s \u2013 %6$s, %8$s %7$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %3$s %2$s, %4$s \u2013 %6$s, %8$s %7$s, %9$s</xliff:g>"</string>
+  <string name="same_year_md1_md2">"<xliff:g id="format">%3$s %2$s \u2013 %8$s %7$s</xliff:g>"</string>
+  <string name="same_year_md1_time1_md2_time2">"<xliff:g id="format">%3$s %2$s, %5$s \u2013 %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_year_mdy1_mdy2">"<xliff:g id="format">%3$s %2$s \u2013 %8$s %7$s, %9$s</xliff:g>"</string>
+  <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s %2$s, %4$s, %5$s \u2013 %8$s %7$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %3$s %2$s, %5$s \u2013 %6$s, %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %3$s %2$s \u2013 %6$s, %8$s %7$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %3$s %2$s, %4$s, %5$s \u2013 %6$s, %8$s %7$s, %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %3$s %2$s \u2013 %6$s, %8$s %7$s, %9$s</xliff:g>"</string>
+  <string name="saturday">Sábado</string>
+  <string name="save_password_label">Confirmar</string>
+  <string name="save_password_message">¿Quiere que el explorador recuerde esta contraseña?</string>
+  <string name="save_password_never">Nunca</string>
+  <string name="save_password_notnow">Ahora no</string>
+  <string name="save_password_remember">Recordar</string>
+  <string name="screen_lock">Bloquear</string>
+  <string name="screen_progress">Trabajando\u2026</string>
+  <string name="search_go">IR</string>
+  <string name="second">segundo</string>
+  <string name="seconds">segundos</string>
+  <string name="selectAll">Seleccionar todo</string>
+  <string name="selectMenuLabel">Seleccionar</string>
+  <string name="sendText">Seleccionar qué hacer con el texto</string>
+  <string name="serviceClassData">Datos</string>
+  <string name="serviceClassDataAsync">Asíncrono</string>
+  <string name="serviceClassDataSync">Sincronizar</string>
+  <string name="serviceClassFAX">FAX</string>
+  <string name="serviceClassPAD">PAD</string>
+  <string name="serviceClassPacket">Paquete</string>
+  <string name="serviceClassSMS">SMS</string>
+  <string name="serviceClassVoice">Voz</string>
+  <string name="serviceDisabled">Servicio deshabilitado</string>
+  <string name="serviceEnabled">Servicio habilitado</string>
+  <string name="serviceEnabledFor">Servicio habilitado para:</string>
+  <string name="serviceErased">Borrado completado</string>
+  <string name="serviceNotProvisioned">Servicio no previsto.</string>
+  <string name="serviceRegistered">Registro completado</string>
+  <string name="silent_mode">Modo silencioso</string>
+  <string name="simAbsentLabel">No hay SIM o está mal insertada</string>
+  <string name="simNetworkPersonalizationLabel">La SIM no se puede usar en este dispositivo</string>
+  <string name="simPINLabel">PIN de SIM necesario (y actualmente no compatible)</string>
+  <string name="simPUKLabel">PUK de SIM necesario (y actualmente no compatible)</string>
+  <string name="status_bar_applications_title">Aplicación</string>
+  <string name="status_bar_date_format">"<xliff:g id="format">d MMMM, yyyy</xliff:g>"</string>
+  <string name="status_bar_latest_events_title">Últimos eventos</string>
+  <string name="status_bar_no_notifications_title">Notificaciones</string>
+  <string name="status_bar_ongoing_events_title">En curso</string>
+  <string name="status_bar_time_format">"<xliff:g id="format">h:mm AA</xliff:g>"</string>
+  <string name="sunday">Domingo</string>
+  <string name="terabyteShort">Tb</string>
+  <string name="thursday">Jueves</string>
+  <string name="time1_time2">"<xliff:g id="format">%1$s \u2013 %2$s</xliff:g>"</string>
+  <string name="time_date">"<xliff:g id="format">%1$s, %3$s</xliff:g>"</string>
+  <string name="time_picker_set">Establecer</string>
+  <string name="time_wday">"<xliff:g id="format">%1$s, %2$s</xliff:g>"</string>
+  <string name="time_wday_date">"<xliff:g id="format">%1$s, %2$s, %3$s</xliff:g>"</string>
+  <string name="today">Hoy</string>
+  <string name="tomorrow">Mañana</string>
+  <string name="tuesday">Martes</string>
+  <string name="turn_off_radio">Apagar radio</string>
+  <string name="turn_on_radio">Encender radio</string>
+  <string name="unknownName">(desconocido)</string>
+  <string name="untitled">&lt;sin título&gt;</string>
+  <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="format">%1$s, %2$s, %3$s \u2013 %4$s, %5$s, %6$s</xliff:g>"</string>
+  <string name="wday1_date1_wday2_date2">"<xliff:g id="format">%1$s, %2$s \u2013 %4$s, %5$s</xliff:g>"</string>
+  <string name="wday_date">"<xliff:g id="format">%2$s, %3$s</xliff:g>"</string>
+  <string name="web_user_agent">Mozilla/5.0 (Linux; U; Android 0.6; en)
+        AppleWebKit/525.10+ (KHTML, como Gecko) Versión/3.0.4 Mobile Safari/523.12.2</string>
+  <string name="wednesday">Miércoles</string>
+  <string name="week">semana</string>
+  <string name="weekly">"Semanal en <xliff:g id="day">%s</xliff:g>"</string>
+  <string name="weekly_format">d MMM</string>
+  <string name="weeks">semanas</string>
+  <string name="whichApplication">¿Qué aplicación desea usar?</string>
+  <string name="yearly">Anual</string>
+  <string name="yearly_format">yyyy</string>
+  <string name="yes">Aceptar</string>
+  <string name="yesterday">Ayer</string>
+</resources>
diff --git a/core/res/res/values-it-rIT/strings.xml b/core/res/res/values-it-rIT/strings.xml
new file mode 100644
index 0000000..412648d
--- /dev/null
+++ b/core/res/res/values-it-rIT/strings.xml
@@ -0,0 +1,818 @@
+<?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="BaMmi">Blocco chiamate</string>
+  <string name="CLIRDefaultOffNextCallOff">Impostazioni predefinite ID chiamante su non limitato. Chiamata successiva: senza limitazioni</string>
+  <string name="CLIRDefaultOffNextCallOn">Impostazioni predefinite ID chiamante su non limitato. Chiamata successiva: con limitazioni</string>
+  <string name="CLIRDefaultOnNextCallOff">Impostazioni predefinite ID chiamante su limitato. Chiamata successiva: senza limitazioni</string>
+  <string name="CLIRDefaultOnNextCallOn">Impostazioni predefinite ID chiamante su limitato. Chiamata successiva: con limitazioni</string>
+  <string name="CLIRPermanent">Impossibile modificare l'impostazione dell'ID chiamante.</string>
+  <string name="CfMmi">Trasferimento chiamata</string>
+  <string name="ClipMmi">ID chiamante in ingresso</string>
+  <string name="ClirMmi">ID chiamante in uscita</string>
+  <string name="CwMmi">Chiamata in attesa</string>
+  <string name="Midnight">"Mezzanotte"</string>
+  <string name="Noon">"Pomeriggio"</string>
+  <string name="PinMmi">Cambio PIN</string>
+  <string name="PwdMmi">Cambio password</string>
+  <string name="VideoView_error_button">OK</string>
+  <string name="VideoView_error_text_unknown">Impossibile riprodurre il video.</string>
+  <string name="VideoView_error_title">Impossibile riprodurre il video</string>
+  <string name="abbrev_month">"<xliff:g id="format">%b</xliff:g>"</string>
+  <string name="abbrev_month_day">"<xliff:g id="format">%-d %b</xliff:g>"</string>
+  <string name="abbrev_month_day_year">"<xliff:g id="format">%-d %b %Y</xliff:g>"</string>
+  <string name="abbrev_month_year">"<xliff:g id="format">%b %Y</xliff:g>"</string>
+  <string name="activate_keyguard">Blocco schermo</string>
+  <string name="aerr_application">L'applicazione <xliff:g id="application">%1$s</xliff:g>
+        (processo <xliff:g id="process">%2$s</xliff:g>) è stata interrotta in maniera imprevista. Riprovare.</string>
+  <string name="aerr_process">Il processo <xliff:g id="process">%1$s</xliff:g> è
+        stata interrotto in maniera imprevista. Riprovare.</string>
+  <string name="aerr_title">Spiacenti.</string>
+  <string name="ago">fa</string>
+  <string name="alwaysUse">Utilizzare come impostazione predefinita per questa azione.</string>
+  <string name="am">"AM"</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="anr_title">Spiacenti!</string>
+  <string name="badPin">Il vecchio PIN digitato non è corretto.</string>
+  <string name="badPuk">Il codice PUK digitato non è corretto.</string>
+  <string name="battery_low_percent_format">Meno di <xliff:g id="number">%d%%</xliff:g>
+    rimanente.</string>
+  <string name="battery_low_subtitle">Batteria quasi scarica:</string>
+  <string name="battery_low_title">Collegare il caricabatterie</string>
+  <string name="battery_status_charging">Sotto carica\u2026</string>
+  <string name="battery_status_text_percent_format">
+					<xliff:g id="number">%d%%</xliff:g>
+				</string>
+  <string name="before">Prima</string>
+  <string name="browserSavedFormData">Salvataggio dei dati del modulo completato.</string>
+  <string name="byteShort">B</string>
+  <string name="cancel">Annulla</string>
+  <string name="capital_off">OFF</string>
+  <string name="capital_on">ON</string>
+  <string name="cfReasonBusy">Trasferimento chiamata: Occupato</string>
+  <string name="cfReasonNR">Trasferimento chiamata: Non raggiungibile</string>
+  <string name="cfReasonNRy">Trasferimento chiamata: Nessuna risposta</string>
+  <string name="cfReasonUnconditional">Trasferimento chiamata: Sempre</string>
+  <string name="cfTemplateForwarded">{0}: {1}</string>
+  <string name="cfTemplateForwardedTime">{0}: {1} dopo {2} secondi</string>
+  <string name="cfTemplateNotForwarded">{0}: Non trasferita</string>
+  <string name="cfTemplateRegistered">{0}: Non trasferita</string>
+  <string name="cfTemplateRegisteredTime">{0}: Non trasferita</string>
+  <string name="chooseActivity">Selezionare un'azione</string>
+  <string name="clearDefaultHintMsg">Cancella impostazione predefinita in Impostazioni Home &gt; Applicazioni &gt; Gestisci applicazioni.</string>
+  <string name="compass_accuracy_banner">La bussola necessita di calibrazione</string>
+  <string name="compass_accuracy_notificaction_body">Scuotere delicatamente il telefono per eseguire la calibrazione.</string>
+  <string name="compass_accuracy_notificaction_title">Calibra bussola</string>
+  <string name="contentServiceSync">Sincronizza</string>
+  <string name="contentServiceSyncErrorNotificationDesc">Problemi nella sincronizzazione.</string>
+  <string name="contentServiceSyncNotificationDesc">Sincronizzazione in corso</string>
+  <string name="contentServiceSyncNotificationTitle">Sincronizza</string>
+  <string name="contentServiceTooManyDeletesNotificationDesc">Troppe eliminazioni di %s.</string>
+  <string name="contentServiceXmppAvailable">XMPP Attivo</string>
+  <string name="copy">Copia</string>
+  <string name="copyAll">Copia tutto</string>
+  <string name="copyUrl">Copia URL</string>
+  <string name="cut">Taglia</string>
+  <string name="cutAll">Taglia tutto</string>
+  <string name="daily">Giornaliero</string>
+  <string name="daily_format">h:mm aa</string>
+  <string name="date1_date2">"<xliff:g id="format">%2$s \u2013 %5$s</xliff:g>"</string>
+  <string name="date1_time1_date2_time2">"<xliff:g id="format">%2$s, %3$s \u2013 %5$s, %6$s</xliff:g>"</string>
+  <string name="date_picker_month">mese</string>
+  <string name="date_picker_set">Imposta</string>
+  <string name="date_range_separator">" \u2013 "</string>
+  <string name="date_time_set">Imposta</string>
+  <string name="day">giorno</string>
+  <string name="day_of_week_long_friday">Venerdì</string>
+  <string name="day_of_week_long_monday">Lunedì</string>
+  <string name="day_of_week_long_saturday">Sabato</string>
+  <string name="day_of_week_long_sunday">Domenica</string>
+  <string name="day_of_week_long_thursday">Giovedì</string>
+  <string name="day_of_week_long_tuesday">Martedì</string>
+  <string name="day_of_week_long_wednesday">Mercoledì</string>
+  <string name="day_of_week_medium_friday">Ven</string>
+  <string name="day_of_week_medium_monday">Lun</string>
+  <string name="day_of_week_medium_saturday">Sab</string>
+  <string name="day_of_week_medium_sunday">Dom</string>
+  <string name="day_of_week_medium_thursday">Gio</string>
+  <string name="day_of_week_medium_tuesday">Mar</string>
+  <string name="day_of_week_medium_wednesday">Mer</string>
+  <string name="day_of_week_short_friday">Ve</string>
+  <string name="day_of_week_short_monday">Lu</string>
+  <string name="day_of_week_short_saturday">S</string>
+  <string name="day_of_week_short_sunday">D</string>
+  <string name="day_of_week_short_thursday">G</string>
+  <string name="day_of_week_short_tuesday">M</string>
+  <string name="day_of_week_short_wednesday">Me</string>
+  <string name="day_of_week_shorter_friday">V</string>
+  <string name="day_of_week_shorter_monday">L</string>
+  <string name="day_of_week_shorter_saturday">S</string>
+  <string name="day_of_week_shorter_sunday">D</string>
+  <string name="day_of_week_shorter_thursday">G</string>
+  <string name="day_of_week_shorter_tuesday">M</string>
+  <string name="day_of_week_shorter_wednesday">M</string>
+  <string name="day_of_week_shortest_friday">F</string>
+  <string name="day_of_week_shortest_monday">M</string>
+  <string name="day_of_week_shortest_saturday">S</string>
+  <string name="day_of_week_shortest_sunday">D</string>
+  <string name="day_of_week_shortest_thursday">G</string>
+  <string name="day_of_week_shortest_tuesday">M</string>
+  <string name="day_of_week_shortest_wednesday">M</string>
+  <string name="days">giorni</string>
+  <string name="daysDurationFuturePlural">tra <xliff:g id="days">%d</xliff:g> giorni</string>
+  <string name="daysDurationPastPlural"><xliff:g id="days">%d</xliff:g> giorni fa</string>
+  <string name="debug">Debug</string>
+  <string name="defaultMsisdnAlphaTag">MSISDN1</string>
+  <string name="defaultVoiceMailAlphaTag">Posta vocale</string>
+  <string name="default_permission_group">Predefinito</string>
+  <string name="elapsed_time_short_format_h_mm_ss">
+					<xliff:g id="format">%1$d:%2$02d:%3$02d</xliff:g>
+				</string>
+  <string name="elapsed_time_short_format_mm_ss">
+					<xliff:g id="format">%1$02d:%2$02d</xliff:g>
+				</string>
+  <string name="ellipsis">\u2026</string>
+  <string name="emergency_call_dialog_call">Chiamata di emergenza</string>
+  <string name="emergency_call_dialog_cancel">Annulla</string>
+  <string name="emergency_call_dialog_number_for_display">Numero di emergenza</string>
+  <string name="emergency_call_dialog_text">Effettuare una chiamata di emergenza?</string>
+  <string name="emergency_call_number_uri">tel:112</string>
+  <string name="emptyPhoneNumber">(Nessun numero telefonico)</string>
+  <string name="every_weekday">"Ogni settimana (Lun\u2013Fri)"</string>
+  <string name="factorytest_failed">Test del produttore non riuscito</string>
+  <string name="factorytest_no_action">Impossibile trovare un pacchetto che fornisca l'azione
+        FACTORY_TEST.</string>
+  <string name="factorytest_not_system">L'azione FACTORY_TEST
+        è supportata solo per i pacchetti installati in /system/app.</string>
+  <string name="factorytest_reboot">Riavvia</string>
+  <string name="force_close">Forza chiusura</string>
+  <string name="friday">Venerdì</string>
+  <string name="gigabyteShort">GB</string>
+  <string name="global_action_lock">Blocco schermo</string>
+  <string name="global_action_power_off">Spegni</string>
+  <string name="global_action_silent_mode_off_status">Suono attivato</string>
+  <string name="global_action_silent_mode_on_status">Suono disattivato</string>
+  <string name="global_action_toggle_silent_mode">Modalità automatica</string>
+  <string name="global_actions">Opzioni telefono</string>
+  <string name="googlewebcontenthelper_loading">Caricamento in corso\u2026</string>
+  <string name="hour">ora</string>
+  <string name="hours">ore</string>
+  <string name="httpError">La pagina Web contiene un errore.</string>
+  <string name="httpErrorAuth">Impossibile completare l'autenticazione.</string>
+  <string name="httpErrorBadUrl">Impossibile aprire la pagina perché l'URL non è valido.</string>
+  <string name="httpErrorConnect">Impossibile stabilire una connessione con il server.</string>
+  <string name="httpErrorFailedSslHandshake">Impossibile stabilire una connessione sicura.</string>
+  <string name="httpErrorFile">Impossibile accedere al file.</string>
+  <string name="httpErrorFileNotFound">Impossibile trovare il file richiesto.</string>
+  <string name="httpErrorIO">Impossibile comunicare con il server. Riprovare più tardi.</string>
+  <string name="httpErrorLookup">Impossibile trovare l'URL.</string>
+  <string name="httpErrorOk">OK</string>
+  <string name="httpErrorProxyAuth">Impossibile completare l'autenticazione tramite il server proxy.</string>
+  <string name="httpErrorRedirectLoop">La pagina contiene troppi reindirizzamenti server.</string>
+  <string name="httpErrorTimeout">La connessione con il server è scaduta.</string>
+  <string name="httpErrorTooManyRequests">Troppe richieste in corso di elaborazione. Riprovare più tardi.</string>
+  <string name="httpErrorUnsupportedAuthScheme">Lo schema di autenticazione del sito non è supportato.</string>
+  <string name="httpErrorUnsupportedScheme">Il protocollo non è supportato.</string>
+  <string name="in">in</string>
+  <string name="invalidPin">Digitare un PIN compreso tra 4 e 8 numeri.</string>
+  <string name="keyguard_label_text">Per sbloccare premere Menu quindi 0.</string>
+  <string name="keyguard_password_emergency_instructions">Premere il pulsante di chiamata per effettuare una chiamata di emergenza.</string>
+  <string name="keyguard_password_enter_pin_code">Immettere il codice PIN:</string>
+  <string name="keyguard_password_instructions">Immettere la password o digitare un numero di emergenza.</string>
+  <string name="keyguard_password_wrong_pin_code">Codice PIN errato.</string>
+  <string name="kilobyteShort">KB</string>
+  <string name="lockscreen_carrier_default">(Nessun servizio)</string>
+  <string name="lockscreen_carrier_key">gsm.operator.alpha</string>
+  <string name="lockscreen_emergency_call">Chiamata di emergenza</string>
+  <string name="lockscreen_failed_attempts_almost_glogin">
+        Il codice di sblocco è stato immesso in maniera errata <xliff:g id="number">%d</xliff:g> volte.
+       Dopo <xliff:g id="number">%d</xliff:g> ulteriori tentativi non riusciti,
+       sarà necessario sbloccare il telefono tramite l'accesso Google.\n\n
+       Riprovare tra <xliff:g id="number">%d</xliff:g> secondi.
+    </string>
+  <string name="lockscreen_forgot_pattern_button_text">Password dimenticata?</string>
+  <string name="lockscreen_glogin_instructions">Per sbloccare,\neffettuare l'accesso con l'account Google:</string>
+  <string name="lockscreen_glogin_invalid_input">Nome utente o password non valida.</string>
+  <string name="lockscreen_glogin_password_hint">Password</string>
+  <string name="lockscreen_glogin_submit_button">Accedi</string>
+  <string name="lockscreen_glogin_too_many_attempts">Troppi tentativi di immissione codice!</string>
+  <string name="lockscreen_glogin_username_hint">Nome utente (e-mail)</string>
+  <string name="lockscreen_instructions_when_pattern_disabled">Premere Menu per sbloccare.</string>
+  <string name="lockscreen_instructions_when_pattern_enabled">Premere Menu per sbloccare o effettuare una chiamata di emergenza.</string>
+  <string name="lockscreen_low_battery">Collegare il caricabatterie.</string>
+  <string name="lockscreen_missing_sim_instructions">Inserire una scheda SIM.</string>
+  <string name="lockscreen_missing_sim_message">Nessuna scheda SIM nel telefono.</string>
+  <string name="lockscreen_missing_sim_message_short">Nessuna scheda SIM.</string>
+  <string name="lockscreen_network_locked_message">Rete bloccata</string>
+  <string name="lockscreen_pattern_correct">Corretto!</string>
+  <string name="lockscreen_pattern_instructions">Digitare il codice per sbloccare:</string>
+  <string name="lockscreen_pattern_wrong">Riprovare:</string>
+  <string name="lockscreen_plugged_in">Sotto carica (<xliff:g id="number">%d%%</xliff:g>)</string>
+  <string name="lockscreen_screen_locked">Blocco schermo</string>
+  <string name="lockscreen_sim_locked_message">La scheda SIM è bloccata.</string>
+  <string name="lockscreen_sim_puk_locked_instructions">Contattare il centro servizi.</string>
+  <string name="lockscreen_sim_puk_locked_message">La scheda SIM è bloccata con il codice PUK.</string>
+  <string name="lockscreen_sim_unlock_progress_dialog_message">Sblocco della scheda SIM in corso\u2026</string>
+  <string name="lockscreen_too_many_failed_attempts_countdown">Riprovare tra <xliff:g id="number">%d</xliff:g> secondi.</string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_message">
+        Il codice di sblocco è stato digitato erroneamente <xliff:g id="number">%d</xliff:g> volte.
+        \n\nRiprovare tra <xliff:g id="number">%d</xliff:g> secondi.
+    </string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_title">Avviso codice blocco</string>
+  <string name="low_internal_storage_view_text">Memoria del telefono insufficiente.</string>
+  <string name="low_internal_storage_view_title">Spazio insufficiente</string>
+  <string name="low_memory">La memoria del telefono è piena. Eliminare alcuni file per liberare spazio.</string>
+  <string name="me">Me</string>
+  <string name="megabyteShort">MB</string>
+  <string name="menu_delete_shortcut_label">elimina</string>
+  <string name="menu_enter_shortcut_label">enter</string>
+  <string name="menu_space_shortcut_label">spazio</string>
+  <string name="midnight">"Mezzanotte"</string>
+  <string name="minute">min</string>
+  <string name="minutes">min</string>
+  <string name="mismatchPin">Il codice PIN inserito è errato.</string>
+  <string name="mmiComplete">MMI completato.</string>
+  <string name="mmiError">Problema di connessione o codice MMI non valido.</string>
+  <string name="monday">Lunedì</string>
+  <string name="month">"<xliff:g id="format">%B</xliff:g>"</string>
+  <string name="month_day">"<xliff:g id="format">%-d %B</xliff:g>"</string>
+  <string name="month_day_year">"<xliff:g id="format">%-d %B %Y</xliff:g>"</string>
+  <string name="month_long_april">Aprile</string>
+  <string name="month_long_august">Agosto</string>
+  <string name="month_long_december">Dicembre</string>
+  <string name="month_long_february">Febbraio</string>
+  <string name="month_long_january">Gennaio</string>
+  <string name="month_long_july">Luglio</string>
+  <string name="month_long_june">Giugno</string>
+  <string name="month_long_march">Marzo</string>
+  <string name="month_long_may">Maggio</string>
+  <string name="month_long_november">Novembre</string>
+  <string name="month_long_october">Ottobre</string>
+  <string name="month_long_september">Settembre</string>
+  <string name="month_medium_april">Apr</string>
+  <string name="month_medium_august">Ago</string>
+  <string name="month_medium_december">Dic</string>
+  <string name="month_medium_february">Feb</string>
+  <string name="month_medium_january">Gen</string>
+  <string name="month_medium_july">Lug</string>
+  <string name="month_medium_june">Giu</string>
+  <string name="month_medium_march">Mar</string>
+  <string name="month_medium_may">Mag</string>
+  <string name="month_medium_november">Nov</string>
+  <string name="month_medium_october">Ott</string>
+  <string name="month_medium_september">Set</string>
+  <string name="month_shortest_april">A</string>
+  <string name="month_shortest_august">A</string>
+  <string name="month_shortest_december">D</string>
+  <string name="month_shortest_february">F</string>
+  <string name="month_shortest_january">G</string>
+  <string name="month_shortest_july">L</string>
+  <string name="month_shortest_june">G</string>
+  <string name="month_shortest_march">M</string>
+  <string name="month_shortest_may">M</string>
+  <string name="month_shortest_november">N</string>
+  <string name="month_shortest_october">O</string>
+  <string name="month_shortest_september">S</string>
+  <string name="month_year">"<xliff:g id="format">%B %Y</xliff:g>"</string>
+  <string name="monthly">Mensile</string>
+  <string name="monthly_format">g MMM</string>
+  <string name="more_item_label">Altro</string>
+  <string name="needPuk2">Digitare il codice PUK2 per sbloccare la scheda SIM.</string>
+  <string name="no">Annulla</string>
+  <string name="noApplications">Nessuna applicazione può eseguire questa azione.</string>
+  <string name="no_permissions">Nessuna autorizzazione richiesta</string>
+  <string name="no_recent_tasks">Nessuna applicazione recente.</string>
+  <string name="noon">"pomeriggio"</string>
+  <string name="numeric_date">"<xliff:g id="format">%d/%m/%Y</xliff:g>"</string>
+  <string name="numeric_date_notation">"<xliff:g id="format">%d/%m/%y</xliff:g>"</string>
+  <string name="numeric_md1_md2">"<xliff:g id="format">%3$s/%2$s \u2013 %8$s/%7$s</xliff:g>"</string>
+  <string name="numeric_md1_time1_md2_time2">"<xliff:g id="format">%3$s/%2$s, %5$s \u2013 %8$s/%7$s, %10$s</xliff:g>"</string>
+  <string name="numeric_mdy1_mdy2">"<xliff:g id="format">%3$s/%2$s/%4$s \u2013 %8$s/%7$s/%9$s</xliff:g>"</string>
+  <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s/%2$s/%4$s, %5$s \u2013 %8$s/%7$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s %3$s/%2$s, %5$s \u2013 %6$s %8$s/%7$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %3$s/%2$s \u2013 %6$s, %8$s/%7$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s %3$s/%2$s/%4$s, %5$s \u2013 %6$s %8$s/%7$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s %3$s/%2$s/%4$s \u2013 %6$s %8$s/%7$s/%9$s</xliff:g>"</string>
+  <string name="ok">OK</string>
+  <string name="oneMonthDurationPast">1 mese fa</string>
+  <string name="open_permission_deny">Non si dispone dell'autorizzazione per aprire questa pagina.</string>
+  <string name="passwordIncorrect">Password errata.</string>
+  <string name="paste">Incolla</string>
+  <string name="permdesc_accessCoarseLocation">Accede alle origini approssimative dell'ubicazione, quali il database in rete
+        del cellulare, per determinare l'ubicazione approssimativa del telefono, quando disponibile. Le applicazioni
+        nocive possono utilizzare questa autorizzazione per determinare l'ubicazione approssimativa dell'utente.</string>
+  <string name="permdesc_accessFineLocation">Accede alle origini accurate dell'ubicazione, ad esempio
+        il sistema GPS (Global Positioning System) sul telefono, quando disponibile.
+        Le applicazioni nocive possono utilizzare questa autorizzazione per determinare l'ubicazione dell'utente,
+        consumando ulteriormente la batteria.</string>
+  <string name="permdesc_accessLocationExtraCommands">Accede ai comandi del provider da altre posizione.
+        Le applicazioni nocive possono utilizzare questa per interferire con il funzionamento del GPS
+        o altre sorgenti ubicazione.</string>
+  <string name="permdesc_accessMockLocation">Crea sorgenti simulate dell'ubicazione a scopo di prova.
+        Le applicazioni nocive possono utilizzare questa autorizzazione per ignorare l'ubicazione e/o lo stato restituito dalle sorgenti reali dell'ubicazione
+,        ad esempio un segnale GPS o un fornitore di servizi di rete.</string>
+  <string name="permdesc_accessNetworkState">Consente ad un'applicazione di visualizzare
+      lo stato di tutte le reti.</string>
+  <string name="permdesc_accessSurfaceFlinger">Consente all'applicazione di utilizzare
+        le applicazioni base di SurfaceFlinger.</string>
+  <string name="permdesc_accessWifiState">Consente ad un'applicazione di visualizzare
+      le informazioni sullo stato Wi-Fi.</string>
+  <string name="permdesc_addSystemService">Consente all'applicazione di pubblicare
+        i propri servizi base del sistema. Le applicazioni nocive possono assumere il controllo
+        del sistema, e sottrarre o danneggiare i dati in esso contenuti.</string>
+  <string name="permdesc_batteryStats">Consente la modifica delle
+        statistiche raccolte sulla batteria. Non utilizzare per applicazioni normali.</string>
+  <string name="permdesc_bluetooth">Consente a un'applicazione di visualizzare
+      la configurazione del telefono locale Bluetooth e di effettuare e accettare connessioni
+      con i dispositivi accoppiati.</string>
+  <string name="permdesc_bluetoothAdmin">Consente a un'applicazione di configurare
+      il telefono locale Bluetooth e di rilevare ed eseguire accoppiamenti con i dispositivi
+      remoti.</string>
+  <string name="permdesc_brick">Consente all'applicazione di
+        disabilitare permanentemente l'intero telefono. Si tratta di un'autorizzazione molto rischiosa.</string>
+  <string name="permdesc_broadcastPackageRemoved">Consente a un'applicazione di
+        trasmettere una notifica quando viene rimosso un pacchetto di applicazioni.
+        Le applicazioni nocive possono utilizzare questa autorizzazione per interrompere tutte le altre applicazioni
+        in esecuzione.</string>
+  <string name="permdesc_broadcastSticky">Consente a un'applicazione di inviare
+        broadcast permanenti che rimangono anche dopo il termine del broadcast.
+        Le applicazioni nocive possono rallentare le prestazioni del telefono e renderlo instabile utilizzando
+        troppa memoria.</string>
+  <string name="permdesc_callPhone">Consente all'applicazione di chiamare
+        numeri telefonici senza interventi. Le applicazioni nocive possono
+        effettuare chiamate indesiderate a carico dell'utente. Si noti che ciò non
+        consente all'applicazione di effettuare chiamate a numeri di emergenza.</string>
+  <string name="permdesc_callPrivileged">Consente all'applicazione di effettuare chiamate a qualsiasi numero,
+        compresi numeri di emergenza senza alcun intervento. Le applicazioni nocive possono
+        effettuare chiamate inutili e illecite verso servizi di
+ emergenza.</string>
+  <string name="permdesc_camera">Consente all'applicazione di scattare foto
+        con la fotocamera. In questo modo è sempre possibile raccogliere le immagini
+        visualizzate dalla fotocamera.</string>
+  <string name="permdesc_changeComponentState">Consente a un'applicazione di apportare modifiche a prescindere dal fatto che
+        il componente di un'altra applicazione sia abilitato o meno. Le applicazioni nocive possono utilizzare
+        questa autorizzazione per disabilitare le principali funzioni del telefono. Utilizzare con cautela l'autorizzazione poiché
+         è possibile che i componenti dell'applicazione si presentino in uno stato inutilizzabile, non uniforme o instabile.
+    </string>
+  <string name="permdesc_changeConfiguration">Consente a un'applicazione di
+        modificare la configurazione corrente, ad esempio le impostazioni internazionali o le dimensioni generali dei
+        caratteri.</string>
+  <string name="permdesc_changeNetworkState">Consente a un'applicazione di modificare
+     lo stato della connettività di rete.</string>
+  <string name="permdesc_changeWifiState">Consente a un'applicazione di connettersi
+      e disconnettersi dai punti di accesso Wi-Fi e apportare modifiche alle reti
+      Wi-Fi configurate.</string>
+  <string name="permdesc_checkinProperties">Consente l'accesso in lettura/scrittura
+        alle proprietà caricate dal servizio di controllo. Non utilizzare per applicazioni
+        normali.</string>
+  <string name="permdesc_clearAppCache">Consente a un'applicazione di liberare memoria nel telefono
+        eliminando i file nella directory della cache dell'applicazione. Normalmente l'accesso al processo del sistema
+        è molto limitato.</string>
+  <string name="permdesc_clearAppUserData">Consente a un'applicazione di cancellare i dati dell'utente.</string>
+  <string name="permdesc_createNetworkSockets">Consente a un'applicazione di
+      creare socket di rete.</string>
+  <string name="permdesc_deleteCacheFiles">Consente a un'applicazione di cancellare
+        i file nella cache.</string>
+  <string name="permdesc_deletePackages">Consente a un'applicazione di cancellare
+        i pacchetti Android. Le applicazioni nocive possono utilizzare questa funzione per cancellare importanti applicazioni.</string>
+  <string name="permdesc_devicePower">Consente all'applicazione di accendere o spegnere
+        il telefono.</string>
+  <string name="permdesc_diagnostic">Consente all'applicazione di leggere e scrivere su
+    qualsiasi risorsa posseduta dal gruppo diag; ad esempio, file in /dev. Questo potrebbe potenzialmente
+    avere effetti sulla stabilità e protezione del sistema. Deve essere utilizzato ESCLUSIVAMENTE
+    per diagnostiche specifiche dell'hardware da parte del costruttore o dell'operatore.</string>
+  <string name="permdesc_disableKeyguard">Consente a un'applicazione di disabilitare
+      il blocco tasti e tutte le password di sicurezza associate. Un esempio appropriato
+      è rappresentato dal telefono che disabilita il blocco tasti alla ricezione di una chiamata,
+      per riabilitarlo al termine della chiamata stessa.</string>
+  <string name="permdesc_dump">Consente all'applicazione di recuperare
+        o stato interno del sistema. Le applicazioni nocive possono recuperare
+        una vasta gamma di informazioni riservate e protette di cui normalmente non avrebbero
+        bisogno.</string>
+  <string name="permdesc_expandStatusBar">Consente all'applicazione di
+        espandere o comprimere la barra di stato.</string>
+  <string name="permdesc_factoryTest">Eseguire un test del produttore di basso livello,
+        consentendo l'accesso completo all'hardware del telefono. Disponibile solo quando
+        il telefono viene eseguito in modalità test del produttore.</string>
+  <string name="permdesc_flashlight">Consente all'applicazione di controllare
+        il flash.</string>
+  <string name="permdesc_forceBack">Consente a un'applicazione di forzare
+        la chiusura di tutte le attività in esecuzione in primo piano e tornare indietro.
+        Non utilizzarlo per le applicazioni normali.</string>
+  <string name="permdesc_fotaUpdate">Consente a un'applicazione di ricevere
+        le notifiche sugli aggiornamenti in sospeso del sistema e ne attiva
+        l'installazione. Le applicazioni nocive possono utilizzare questa funzionalità per danneggiare il sistema con aggiornamenti non autorizzati
+        o interferire in generale con il processo di
+        aggiornamento.</string>
+  <string name="permdesc_getAccounts">Consente a un'applicazione di ottenere
+      l'elenco degli account del telefono.</string>
+  <string name="permdesc_getPackageSize">Consente a un'applicazione di ricevere
+        il proprio codice, i dati e la dimensione della cache</string>
+  <string name="permdesc_getTasks">Consente a un'applicazione di ricevere
+        informazioni sui task in esecuzione attualmente e recentemente. Ciò può consentire
+        alle applicazioni nocive di rilevare informazioni riservate sulle altre applicazioni.</string>
+  <string name="permdesc_hardware_test">Consente all'applicazione di controllare le varie periferiche
+        per testare l'hardware.</string>
+  <string name="permdesc_injectEvents">Consente a un'applicazione di distribuire
+        i propri eventi di input (pressione dei tasti e così via) ad altre applicazioni. Le applicazioni nocive
+        possono utilizzare questa autorizzazione per prendere il controllo del telefono.</string>
+  <string name="permdesc_installPackages">Consente a un'applicazione di installare
+        pacchetti di Android nuovi o aggiornati. Le applicazioni nocive possono utilizzarla per aggiungere nuove applicazioni con
+        autorizzazioni più o meno efficaci.</string>
+  <string name="permdesc_internalSystemWindow">Consente la creazione di
+        finestre che devono essere utilizzate dall'interfaccia utente
+        del sistema interno. Non utilizzare per applicazioni normali.</string>
+  <string name="permdesc_locationUpdates">Consente di attivare/disattivare le notifiche
+        di aggiornamento della posizione dalla radio. Non utilizzare per applicazioni normali.</string>
+  <string name="permdesc_manageAppTokens">Consente alle applicazioni di
+        creare e gestire i propri token, aggirando il loro normale
+        Z-ordering. Non utilizzare mai per applicazioni normali.</string>
+  <string name="permdesc_masterClear">Consente a un'applicazione di ripristinare completamente
+        le impostazioni di fabbrica del sistema, cancellando tutti i dati,
+        la configurazione e le applicazioni installate.</string>
+  <string name="permdesc_modifyAudioSettings">Consente all'applicazione di modificare
+        le impostazioni audio globali ad esempio il volume e l'indirizzamento.</string>
+  <string name="permdesc_modifyPhoneState">Consente all'applicazione di controllare
+        le funzioni telefoniche del dispositivo. Un'applicazione con questa autorizzazione può scambiare le reti,
+        accendere o spegnere il telefono ed eseguire operazioni simili senza
+        notifiche.</string>
+  <string name="permdesc_mount_unmount_filesystems">Consente all'applicazione di montare e smontare
+        i file system per i dispositivi rimovibili.</string>
+  <string name="permdesc_persistentActivity">Consente a un'applicazione di rendere
+        permanenti alcuni suoi componenti in modo che il sistema non possa utilizzarli per altre
+        applicazioni.</string>
+  <string name="permdesc_processOutgoingCalls">Consente all'applicazione di
+        elaborare le chiamate in uscita e modificare il numero da comporre. Le applicazioni nocive
+        possono monitorare, reindirizzare o impedire le chiamate in uscita.</string>
+  <string name="permdesc_readCalendar">Consente a un'applicazione di leggere
+        tutti gli eventi del calendario memorizzati nel telefono. Le applicazioni nocive
+        possono utilizzare questa autorizzazione per inviare gli eventi ad altre persone.</string>
+  <string name="permdesc_readContacts">Consente a un'applicazione di leggere
+        tutti i contatti (indirizzi) memorizzati sul telefono. Le applicazioni nocive
+        possono utilizzare questa autorizzazione per inviare i dati ad altre persone.</string>
+  <string name="permdesc_readFrameBuffer">Consente all'applicazione di leggere
+        il contenuto del buffer del frame.</string>
+  <string name="permdesc_readInputState">Consente alle applicazioni di controllare
+        i tasti premuti anche durante l'interazione con un'altra applicazione (come
+        quando si digita una password). Non utilizzare mai per le normali applicazioni.</string>
+  <string name="permdesc_readLogs">Consente a un'applicazione di leggere dai
+        vari file di log del sistema. Ciò consente di rilevare le informazioni generali
+        sull'attuale indirizzo del telefono. Non dovrebbero contenere
+        informazioni personali o riservate.</string>
+  <string name="permdesc_readOwnerData">Consente a un'applicazione di leggere
+        i dati del proprietario del telefono memorizzati in quest'ultimo. Le applicazioni nocive
+        possono utilizzare questa autorizzazione per leggere i dati del proprietario del telefono.</string>
+  <string name="permdesc_readPhoneState">Consente all'applicazione di accedere alle funzioni telefoniche del dispositivo.
+        Un'applicazione con quest'autorizzazione può rilevare il numero di telefono,
+        se una chiamata è attiva, il numero dell'altro telefono connesso
+        e così via.</string>
+  <string name="permdesc_readSms">Consente all'applicazione di leggere
+      i messaggi SMS memorizzati sul telefono o sulla scheda SIM. Le applicazioni nocive
+      possono leggere i messaggi riservati.</string>
+  <string name="permdesc_readSyncSettings">Consente a un'applicazione di leggere le impostazioni di sincronizzazione,
+        ad esempio se la sincronizzazione per i contatti è abilitata.</string>
+  <string name="permdesc_readSyncStats">Consente a un'applicazione di leggere le statistiche di sincronizzazione, ad esempio
+        la cronologia delle sincronizzazioni avvenute.</string>
+  <string name="permdesc_reboot">Consente all'applicazione di
+        forzare il riavvio del telefono.</string>
+  <string name="permdesc_receiveBootCompleted">Consente a un'applicazione di
+        avviarsi automaticamente dopo l'avvio del sistema.
+        Ciò può prolungare l'avvio del telefono e rallentarne
+        il normale funzionamento a causa dell'esecuzione costante delle applicazioni.</string>
+  <string name="permdesc_receiveMms">Consente all'applicazione di ricevere
+      ed elaborare i messaggi MMS. Le applicazioni nocive possono monitorare i messaggi
+      o eliminarli automaticamente.</string>
+  <string name="permdesc_receiveSms">Consente all'applicazione di ricevere
+      ed elaborare i messaggi SMS. Le applicazioni nocive possono monitorare
+      i messaggi o eliminarli automaticamente.</string>
+  <string name="permdesc_receiveWapPush">Consente all'applicazione di ricevere
+      ed elaborare i messaggi WAP. Le applicazioni nocive possono monitorare
+      i messaggi o eliminarli automaticamente.</string>
+  <string name="permdesc_recordAudio">Consente all'applicazione di accedere
+        al percorso di registrazione audio.</string>
+  <string name="permdesc_reorderTasks">Consente a un'applicazione di spostare
+        i task in primo e in secondo piano. Le applicazioni nocive possono forzare
+        la propria visualizzazione in primo piano automaticamente.</string>
+  <string name="permdesc_restartPackages">Consente a un'applicazione di
+        riavviare forzatamente altre applicazioni.</string>
+  <string name="permdesc_runSetActivityWatcher">Consente a un'applicazione di
+        monitorare e controllare come il sistema avvia le attività.
+        Le applicazioni nocive possono compromettere completamente il sistema. Questa
+        autorizzazione è necessaria solo per lo sviluppo, mai per il normale utilizzo
+        del telefono.</string>
+  <string name="permdesc_sendSms">Consente all'applicazione di inviare messaggi
+      SMS. Le applicazioni nocive possono inviare messaggi senza
+       conferma a carico del proprietario del telefono.</string>
+  <string name="permdesc_setAlwaysFinish">Consente a un'applicazione
+        di controllare se le attività vengono sempre completate subito dopo essere andate in secondo piano.
+        Mai necessario per le applicazioni normali.</string>
+  <string name="permdesc_setAnimationScale">Consente all'applicazione di modificare
+        la velocità di animazione globale, ovvero rallentare o accelerare le animazioni, in qualsiasi momento.</string>
+  <string name="permdesc_setDebugApp">Consente a un'applicazione di attivare
+        il debug per un'altra applicazione. Le applicazioni nocive possono utilizzare
+        questa autorizzazione per interrompere le altre applicazioni.</string>
+  <string name="permdesc_setOrientation">Consente a un'applicazione di modificare
+        la rotazione dello schermo in qualsiasi momento. Mai necessario per
+        le normali applicazioni.</string>
+  <string name="permdesc_setPreferredApplications">Consente a un'applicazione di
+        modificare le applicazioni preferite. In questo modo le applicazioni nocive
+        possono modificare automaticamente le applicazioni in esecuzione, obbligandole
+        a raccogliere dati riservati.</string>
+  <string name="permdesc_setProcessForeground">Consente a un'applicazione di portare
+        in primo piano tutti i processi in esecuzione, in modo da non poterli interrompere.
+        Mai necessaria per le normali applicazioni.</string>
+  <string name="permdesc_setProcessLimit">Consente a un'applicazione
+        di controllare il numero massimo di processi in esecuzione. Mai
+        necessaria per le normali applicazioni.</string>
+  <string name="permdesc_setTimeZone">Consente a un'applicazione di
+        cambiare il fuso orario del telefono.</string>
+  <string name="permdesc_setWallpaper">Consente all'applicazione
+        di impostare lo sfondo del sistema.</string>
+  <string name="permdesc_setWallpaperHints">Consente all'applicazione
+        di impostare i suggerimenti sulle dimensioni dello sfondo del sistema.</string>
+  <string name="permdesc_signalPersistentProcesses">Consente all'applicazione di richiedere
+        che il segnale fornito venga inviato a tutti i processi permanenti.</string>
+  <string name="permdesc_statusBar">Consente all'applicazione di disabilitare
+        la barra di stato o di aggiungere o rimuovere le icone del sistema.</string>
+  <string name="permdesc_subscribedFeedsRead">Consente all'applicazione di visualizzare i dettagli in merito ai feed correntemente sincornizzati.</string>
+  <string name="permdesc_subscribedFeedsWrite">Consente all'applicazione di modificare 
+      i feed correntemente sincronizzati. Questo potrebbe consentire ad applicazioni nocive di 
+      cambiare i feed sincronizzati.</string>
+  <string name="permdesc_systemAlertWindow">Consente a un'applicazione di
+        mostrare le finestre di avviso del sistema. Le applicazioni nocive possono prendere il controllo
+        di tutto lo schermo del telefono.</string>
+  <string name="permdesc_vibrate">Consente all'applicazione di controllare
+        la vibrazione.</string>
+  <string name="permdesc_wakeLock">Consente all'applicazione di evitare che il telefono
+        entri in modalità standby.</string>
+  <string name="permdesc_writeApnSettings">Consente all'applicazione di modificare le impostazioni APN,
+        ad esempio Proxy e Porta si qualsiasi APN.</string>
+  <string name="permdesc_writeCalendar">Consente a un'applicazione di modificare
+        gli eventi del calendario memorizzati nel telefono. Le applicazioni nocive
+        possono utilizzare questa autorizzazione per cancellare o modificare i dati del calendario.</string>
+  <string name="permdesc_writeContacts">Consente a un'applicazione di modificare
+        i dati (indirizzo) dei contatti salvati sul telefono. Le applicazioni nocive
+        possono utilizzare questa autorizzazione per cancellare o modificare i dati del contatto.</string>
+  <string name="permdesc_writeGservices">Consente all'applicazione di modificare
+        i servizi mappa di Google. Non utilizzare per applicazioni normali.</string>
+  <string name="permdesc_writeOwnerData">Consente a un'applicazione di modificare i dati
+        del proprietario del telefono. Le applicazioni nocive
+        possono utilizzare questa autorizzazioni per cancellare o modificare i dati del proprietario.</string>
+  <string name="permdesc_writeSettings">Consente a un'applicazione di modificare i dati
+        delle impostazioni del sistema. Le applicazioni nocive possono danneggiare la configurazione
+        del sistema.</string>
+  <string name="permdesc_writeSms">Consente all'applicazione di scrivere
+      ai messaggi SMS memorizzati sul telefono o sulla scheda SIM. Le applicazioni
+      nocive possono eliminare i messaggi riservati.</string>
+  <string name="permdesc_writeSyncSettings">Consente a un'applicazione di modificare le impostazioni di sincronizzazione,
+        ad esempio se la sincronizzazione per i Contatti è abilitata.</string>
+  <string name="permgroupdesc_accounts">Accedere agli account di Google disponibili.</string>
+  <string name="permgroupdesc_costMoney">Consente alle applicazioni di eseguire queste operazioni
+        a carico dell'utente.</string>
+  <string name="permgroupdesc_developmentTools">Funzioni necessarie solo per gli
+        sviluppatori di applicazioni.</string>
+  <string name="permgroupdesc_hardwareControls">Accedere direttamente all'hardware della
+        cuffia.</string>
+  <string name="permgroupdesc_location">Monitorare l'ubicazione fisica</string>
+  <string name="permgroupdesc_messages">Leggere e scrivere i messaggi SMS,
+        e-mail e altri ancora.</string>
+  <string name="permgroupdesc_network">Consente alle applicazioni di accedere alle varie
+        funzioni di rete.</string>
+  <string name="permgroupdesc_personalInfo">Accedere direttamente ai contatti
+        e al calendario memorizzati nel telefono.</string>
+  <string name="permgroupdesc_phoneCalls">Monitorare, registrare ed elaborare
+        le telefonate.</string>
+  <string name="permgroupdesc_systemTools">Accesso di basso livello e controllo
+        del sistema.</string>
+  <string name="permgrouplab_accounts">Account Google</string>
+  <string name="permgrouplab_costMoney">Servizi a carico dell'utente</string>
+  <string name="permgrouplab_developmentTools">Strumenti di sviluppo</string>
+  <string name="permgrouplab_hardwareControls">Controlli hardware</string>
+  <string name="permgrouplab_location">Località</string>
+  <string name="permgrouplab_messages">Messaggi</string>
+  <string name="permgrouplab_network">Comunicazione di rete</string>
+  <string name="permgrouplab_personalInfo">Informazioni personali</string>
+  <string name="permgrouplab_phoneCalls">Chiamate telefoniche</string>
+  <string name="permgrouplab_systemTools">Strumenti del sistema</string>
+  <string name="permissions_format"><xliff:g id="perm_line1">%1$s</xliff:g>, <xliff:g id="perm_line2">%2$s</xliff:g></string>
+  <string name="permlab_accessCoarseLocation">Ubicazione (basata su rete) approssimativa</string>
+  <string name="permlab_accessFineLocation">Ubicazione (GPS) accurata</string>
+  <string name="permlab_accessLocationExtraCommands">accesso ai comandi del provider da altre posizione</string>
+  <string name="permlab_accessMockLocation">origini ubicazioni simulate a scopo di prova</string>
+  <string name="permlab_accessNetworkState">visualizza stato rete</string>
+  <string name="permlab_accessSurfaceFlinger">accesso SurfaceFlinger</string>
+  <string name="permlab_accessWifiState">visualizza stato Wi-Fi</string>
+  <string name="permlab_addSystemService">pubblica servizi basso livello</string>
+  <string name="permlab_batteryStats">modifica statistiche batteria</string>
+  <string name="permlab_bluetooth">crea connessioni Bluetooth</string>
+  <string name="permlab_bluetoothAdmin">amministrazione bluetooth</string>
+  <string name="permlab_brick">disabilita telefono permanentemente</string>
+  <string name="permlab_broadcastPackageRemoved">invia broadcast rimosso pacchetto</string>
+  <string name="permlab_broadcastSticky">invia broadcast permanente</string>
+  <string name="permlab_callPhone">chiama direttamente numeri di telefono</string>
+  <string name="permlab_callPrivileged">chiamare direttamente numeri di telefono</string>
+  <string name="permlab_camera">scatta foto</string>
+  <string name="permlab_changeComponentState">abilita o disabilita i componenti dell'applicazione</string>
+  <string name="permlab_changeConfiguration">modifica impostazioni IU</string>
+  <string name="permlab_changeNetworkState">cambia connettività di rete</string>
+  <string name="permlab_changeWifiState">cambia stato Wi-Fi</string>
+  <string name="permlab_checkinProperties">accesso alle proprietà di controllo</string>
+  <string name="permlab_clearAppCache">elimina tutti i dati della cache dell'applicazione</string>
+  <string name="permlab_clearAppUserData">elimina altri dati dell'applicazione</string>
+  <string name="permlab_createNetworkSockets">accesso completo a Internet</string>
+  <string name="permlab_deleteCacheFiles">elimina altra cache dell'applicazione</string>
+  <string name="permlab_deletePackages">elimina applicazioni</string>
+  <string name="permlab_devicePower">accendi o spegni telefono</string>
+  <string name="permlab_diagnostic">lettura/scrittura su risorse possedute tramite diag</string>
+  <string name="permlab_disableKeyguard">disabilita blocco tasti</string>
+  <string name="permlab_dump">recupera stato interno del sistema</string>
+  <string name="permlab_expandStatusBar">espandi/comprimi barra di stato</string>
+  <string name="permlab_factoryTest">esegui in modalità test del produttore</string>
+  <string name="permlab_flashlight">controlla flash</string>
+  <string name="permlab_forceBack">forza chiusura applicazione</string>
+  <string name="permlab_fotaUpdate">installa automaticamente aggiornamenti del sistema</string>
+  <string name="permlab_getAccounts">rileva account noti</string>
+  <string name="permlab_getPackageSize">misura spazio memoria applicazione</string>
+  <string name="permlab_getTasks">recupera applicazioni in esecuzione</string>
+  <string name="permlab_hardware_test">hardware di prova</string>
+  <string name="permlab_injectEvents">premere i tasti e i pulsanti di controllo</string>
+  <string name="permlab_installPackages">installa direttamente applicazioni</string>
+  <string name="permlab_internalSystemWindow">visualizza finestre non autorizzate</string>
+  <string name="permlab_locationUpdates">controllo notifiche aggiornamento posizione</string>
+  <string name="permlab_manageAppTokens">gestisci token applicazioni</string>
+  <string name="permlab_masterClear">ripristina valori predefiniti di fabbrica</string>
+  <string name="permlab_modifyAudioSettings">modifica impostazioni audio</string>
+  <string name="permlab_modifyPhoneState">modifica stato telefono</string>
+  <string name="permlab_mount_unmount_filesystems">monta e smonta file system</string>
+  <string name="permlab_persistentActivity">applicazione sempre in esecuzione</string>
+  <string name="permlab_processOutgoingCalls">intercetta chiamate in uscita</string>
+  <string name="permlab_readCalendar">leggi dati del calendario</string>
+  <string name="permlab_readContacts">leggi dati contatti</string>
+  <string name="permlab_readFrameBuffer">leggi buffer frame</string>
+  <string name="permlab_readInputState">registra contenuto immesso e azioni eseguite</string>
+  <string name="permlab_readLogs">leggi file log del sistema</string>
+  <string name="permlab_readOwnerData">leggi dati proprietario</string>
+  <string name="permlab_readPhoneState">leggi stato telefono</string>
+  <string name="permlab_readSms">leggi SMS o MMS</string>
+  <string name="permlab_readSyncSettings">leggi impostazioni di sincronizzazione</string>
+  <string name="permlab_readSyncStats">leggi statistiche di sincronizzazione</string>
+  <string name="permlab_reboot">forzare il riavvio del telefono</string>
+  <string name="permlab_receiveBootCompleted">avvia automaticamente all'avvio</string>
+  <string name="permlab_receiveMms">ricevi MMS</string>
+  <string name="permlab_receiveSms">ricevi SMS</string>
+  <string name="permlab_receiveWapPush">ricevi WAP</string>
+  <string name="permlab_recordAudio">registra audio</string>
+  <string name="permlab_reorderTasks">riordina applicazioni in esecuzione</string>
+  <string name="permlab_restartPackages">riavvia altre applicazioni</string>
+  <string name="permlab_runSetActivityWatcher">monitora e controlla tutto l'avvio dell'applicazione</string>
+  <string name="permlab_sendSms">invia messaggi SMS</string>
+  <string name="permlab_setAlwaysFinish">chiudi tutte le applicazioni in secondo piano</string>
+  <string name="permlab_setAnimationScale">modifica velocità di animazione globale</string>
+  <string name="permlab_setDebugApp">abilita debug applicazione</string>
+  <string name="permlab_setOrientation">cambia orientamento schermo</string>
+  <string name="permlab_setPreferredApplications">imposta applicazioni preferite</string>
+  <string name="permlab_setProcessForeground">impedisci interruzione</string>
+  <string name="permlab_setProcessLimit">limita numero di processi in esecuzione</string>
+  <string name="permlab_setTimeZone">imposta fuso orario</string>
+  <string name="permlab_setWallpaper">imposta sfondo</string>
+  <string name="permlab_setWallpaperHints">imposta suggerimenti dimensioni sfondo</string>
+  <string name="permlab_signalPersistentProcesses">invia segnali Linux ad applicazioni</string>
+  <string name="permlab_statusBar">disabilita o modifica barra di stato</string>
+  <string name="permlab_subscribedFeedsRead">leggere i feed sottoscritti</string>
+  <string name="permlab_subscribedFeedsWrite">scrivere i feed sottoscritti</string>
+  <string name="permlab_systemAlertWindow">visualizza avvisi a livello di sistema</string>
+  <string name="permlab_vibrate">controllo vibrazione</string>
+  <string name="permlab_wakeLock">impedisci standby telefono</string>
+  <string name="permlab_writeApnSettings">scrivere le impostazioni nome punto di accesso</string>
+  <string name="permlab_writeCalendar">scrivi dati calendario</string>
+  <string name="permlab_writeContacts">scrivi dati contatto</string>
+  <string name="permlab_writeGservices">modificare i servizi mappa di Google</string>
+  <string name="permlab_writeOwnerData">scrivi dati proprietario</string>
+  <string name="permlab_writeSettings">modifica impostazioni globali sistema</string>
+  <string name="permlab_writeSms">modifica SMS o MMS</string>
+  <string name="permlab_writeSyncSettings">scrivi impostazioni di sincronizzazione</string>
+  <string name="perms_hide">
+					<xliff:g ctype="bold">Nascondi</xliff:g>
+				</string>
+  <string name="perms_show_all">
+					<xliff:g ctype="bold">Mostra tutti</xliff:g>
+				</string>
+  <string name="petabyteShort">PB</string>
+  <string name="pm">"PM"</string>
+  <string name="power_dialog">Opzioni telefono</string>
+  <string name="power_off">Spegni</string>
+  <string name="prepend_shortcut_label">Menu+</string>
+  <string name="preposition_for_date">su %s</string>
+  <string name="preposition_for_time">a %s</string>
+  <string name="preposition_for_year">in %s</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_picker_title">Selezionare una suoneria</string>
+  <string name="ringtone_silent">Automatico</string>
+  <string name="ringtone_unknown">Suoneria sconosciuta</string>
+  <string name="safeMode">Modalità sicura</string>
+  <string name="same_month_md1_md2">"<xliff:g id="format">%3$s \u2013 %8$s %2$s </xliff:g>"</string>
+  <string name="same_month_md1_time1_md2_time2">"<xliff:g id="format">%3$s %2$s %5$s \u2013 %8$s %7$s %10$s</xliff:g>"</string>
+  <string name="same_month_mdy1_mdy2">"<xliff:g id="format">%3$s \u2013 %8$s %9$s %2$s</xliff:g>"</string>
+  <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s %2$s %4$s, %5$s \u2013 %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %3$s %2$s, %5$s \u2013 %6$s, %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s %3$s %2$s \u2013 %6$s %8$s %7$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %3$s %2$s %4$s, %5$s \u2013 %6$s, %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s %3$s %2$s %4$s \u2013 %6$s %8$s %7$s %9$s</xliff:g>"</string>
+  <string name="same_year_md1_md2">"<xliff:g id="format">%3$s %2$s \u2013 %8$s %7$s</xliff:g>"</string>
+  <string name="same_year_md1_time1_md2_time2">"<xliff:g id="format">%3$s %2$s, %5$s \u2013 %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_year_mdy1_mdy2">"<xliff:g id="format">%3$s %2$s \u2013 %8$s %7$s %9$s</xliff:g>"</string>
+  <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s %2$s %4$s, %5$s \u2013 %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s %3$s %2$s %5$s \u2013 %6$s, %8$s %7$s %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s %3$s %2$s \u2013 %6$s %8$s %7$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s %3$s %2$s %4$s, %5$s \u2013 %6$s %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %3$s %2$s \u2013 %6$s, %8$s %7$s %9$s</xliff:g>"</string>
+  <string name="saturday">Sabato</string>
+  <string name="save_password_label">Conferma</string>
+  <string name="save_password_message">Memorizzare la password nel browser?</string>
+  <string name="save_password_never">Mai</string>
+  <string name="save_password_notnow">Non adesso</string>
+  <string name="save_password_remember">Memorizza</string>
+  <string name="screen_lock">Blocco schermo</string>
+  <string name="screen_progress">In funzione\u2026</string>
+  <string name="search_go">Cerca</string>
+  <string name="second">sec</string>
+  <string name="seconds">sec</string>
+  <string name="selectAll">Seleziona tutto</string>
+  <string name="selectMenuLabel">Seleziona</string>
+  <string name="select_character">Selezionare il carattere da inserire</string>
+  <string name="sendText">Selezionare un'azione per il testo</string>
+  <string name="serviceClassData">Dati</string>
+  <string name="serviceClassDataAsync">Asinc</string>
+  <string name="serviceClassDataSync">Sincronizza</string>
+  <string name="serviceClassFAX">FAX</string>
+  <string name="serviceClassPAD">PAD</string>
+  <string name="serviceClassPacket">Pacchetto</string>
+  <string name="serviceClassSMS">SMS</string>
+  <string name="serviceClassVoice">Voce</string>
+  <string name="serviceDisabled">Il servizio è stato disabilitato.</string>
+  <string name="serviceEnabled">Il servizio è stato abilitato.</string>
+  <string name="serviceEnabledFor">Il servizio è stato abilitato per:</string>
+  <string name="serviceErased">Cancellazione completata.</string>
+  <string name="serviceNotProvisioned">Servizio non fornito.</string>
+  <string name="serviceRegistered">Registrazione completata.</string>
+  <string name="shutdown_confirm">Il telefono verrà spento.</string>
+  <string name="shutdown_progress">Spegnimento in corso\u2026</string>
+  <string name="silent_mode">Modalità automatica</string>
+  <string name="simAbsentLabel">Scheda SIM assente o inserita in maniera errata.</string>
+  <string name="simNetworkPersonalizationLabel">Scheda SIM non utilizzata in questo telefono.</string>
+  <string name="simPINLabel">PIN SIM necessario (e attualmente non supportato).</string>
+  <string name="simPUKLabel">PUK SIM necessario (e attualmente non supportato).</string>
+  <string name="sms_control_default_app_name">Applicazione sconosciuta</string>
+  <string name="sms_control_message">Sono in corso di invio numerosi messaggi SMS. Scegliere \"OK\" per continuare oppure \"Annulla\" per interrompere l'invio.</string>
+  <string name="sms_control_no">Annulla</string>
+  <string name="sms_control_title">Invio messaggi SMS in corso</string>
+  <string name="sms_control_yes">OK</string>
+  <string name="status_bar_applications_title">Applicazione</string>
+  <string name="status_bar_clear_all_button">Cancella notifiche</string>
+  <string name="status_bar_date_format">"<xliff:g id="format">MMMM g, aaaa</xliff:g>"</string>
+  <string name="status_bar_latest_events_title">Notifiche</string>
+  <string name="status_bar_no_notifications_title">Nessuna notifica</string>
+  <string name="status_bar_ongoing_events_title">In uscita</string>
+  <string name="status_bar_time_format">"<xliff:g id="format">h:mm AA</xliff:g>"</string>
+  <string name="sunday">Domenica</string>
+  <string name="terabyteShort">TB</string>
+  <string name="text_copied">Testo copiato negli Appunti.</string>
+  <string name="thursday">Giovedì</string>
+  <string name="time1_time2">"<xliff:g id="format">%1$s \u2013 %2$s</xliff:g>"</string>
+  <string name="time_date">"<xliff:g id="format">%1$s, %3$s</xliff:g>"</string>
+  <string name="time_picker_set">Imposta</string>
+  <string name="time_wday">"<xliff:g id="format">%1$s, %2$s</xliff:g>"</string>
+  <string name="time_wday_date">"<xliff:g id="format">%1$s, %2$s, %3$s</xliff:g>"</string>
+  <string name="today">Oggi</string>
+  <string name="tomorrow">Domani</string>
+  <string name="tuesday">Martedì</string>
+  <string name="turn_off_radio">Disattiva wireless</string>
+  <string name="turn_on_radio">Attiva wireless</string>
+  <string name="unknownName">(Sconosciuto)</string>
+  <string name="untitled">&lt;senza titolo&gt;</string>
+  <string name="volume_alarm">Volume allarme</string>
+  <string name="volume_call">Volume chiamata in ingresso</string>
+  <string name="volume_music">Volume musica/video</string>
+  <string name="volume_ringtone">Volume suoneria</string>
+  <string name="volume_unknown">Volume</string>
+  <string name="wait">Attendi</string>
+  <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="format">%1$s %2$s, %3$s \u2013 %4$s %5$s, %6$s</xliff:g>"</string>
+  <string name="wday1_date1_wday2_date2">"<xliff:g id="format">%1$s %2$s \u2013 %4$s %5$s</xliff:g>"</string>
+  <string name="wday_date">"<xliff:g id="format">%2$s %3$s</xliff:g>"</string>
+  <string name="wednesday">Mercoledì</string>
+  <string name="week">settimana</string>
+  <string name="weekly">"Settimanalmente il <xliff:g id="day">%s</xliff:g>"</string>
+  <string name="weekly_format">MMM g</string>
+  <string name="weeks">settimane</string>
+  <string name="whichApplication">Completa azione utilizzando</string>
+  <string name="year">anno</string>
+  <string name="yearly">Annualmente</string>
+  <string name="yearly_format">aaaa</string>
+  <string name="years">anni</string>
+  <string name="yes">OK</string>
+  <string name="yesterday">Ieri</string>
+</resources>
diff --git a/core/res/res/values-nl-rNL/strings.xml b/core/res/res/values-nl-rNL/strings.xml
new file mode 100644
index 0000000..bca994b
--- /dev/null
+++ b/core/res/res/values-nl-rNL/strings.xml
@@ -0,0 +1,558 @@
+<?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="BaMmi">Oproep blokkeren</string>
+  <string name="CLIRDefaultOffNextCallOff">ID-beperkingsstandaarden op onbeperkt. Volgend gesprek: niet beperkt</string>
+  <string name="CLIRDefaultOffNextCallOn">ID-beperkingsstandaarden op onbeperkt. Volgend gesprek: beperkt</string>
+  <string name="CLIRDefaultOnNextCallOff">ID-beperkingsstandaarden op beperkt. Volgend gesprek: niet beperkt</string>
+  <string name="CLIRDefaultOnNextCallOn">ID-beperkingsstandaarden op beperkt. Volgend gesprek: beperkt</string>
+  <string name="CLIRPermanent">ID-beperking ingesteld op permanente modus.</string>
+  <string name="CfMmi">Oproep doorschakelen</string>
+  <string name="ClipMmi">Nummerweergave inkomend gesprek</string>
+  <string name="ClirMmi">Nummerweergave uitgaand gesprek</string>
+  <string name="CwMmi">Gesprek in wachtstand</string>
+  <string name="Midnight">"Middernacht"</string>
+  <string name="Noon">"12 uur 'smiddags"</string>
+  <string name="PinMmi">Pincode wijzigen</string>
+  <string name="PwdMmi">Wachtwoord wijzigen</string>
+  <string name="VideoView_error_button">OK</string>
+  <string name="VideoView_error_text_unknown">Fout opgetreden bij afspelen van geselecteerde video.</string>
+  <string name="VideoView_error_title">Videoafspeelfout</string>
+  <string name="abbrev_month">"<xliff:g id="format">%b</xliff:g>"</string>
+  <string name="abbrev_month_day">"<xliff:g id="format">%-d %b</xliff:g>"</string>
+  <string name="abbrev_month_day_year">"<xliff:g id="format">%-d %b %Y</xliff:g>"</string>
+  <string name="abbrev_month_year">"<xliff:g id="format">%b %Y</xliff:g>"</string>
+  <string name="activate_keyguard">Toetsbescherming activeren</string>
+  <string name="ago">geleden</string>
+  <string name="alwaysUse">Deze toepassing altijd gebruiken voor deze toepassing</string>
+  <string name="am">"AM"</string>
+  <string name="battery_low_percent_format">Minder dan <xliff:g id="number">%d%%</xliff:g>
+    resterend</string>
+  <string name="battery_low_subtitle">Lage batterijstroom</string>
+  <string name="battery_low_title">Oplader verbinden</string>
+  <string name="battery_status_charging">Bezig met opladen\u2026</string>
+  <string name="battery_status_text_percent_format"><xliff:g id="number">%d%%</xliff:g></string>
+  <string name="before">Voor</string>
+  <string name="browserSavedFormData">Formuliergegevens opgeslagen</string>
+  <string name="byteShort">b</string>
+  <string name="cancel">Annuleren</string>
+  <string name="capital_off">UIT</string>
+  <string name="capital_on">AAN</string>
+  <string name="cfReasonBusy">Oproep doorschakelen bezig</string>
+  <string name="cfReasonNR">Oproep doorschakelen niet bereikbaar</string>
+  <string name="cfReasonNRy">Oproep doorschakelen geen antwoord</string>
+  <string name="cfReasonUnconditional">Oproep doorschakelen onvoorwaardelijk</string>
+  <string name="cfTemplateForwarded">{0}: {1}</string>
+  <string name="cfTemplateForwardedTime">{0}: {1} na {2} seconden</string>
+  <string name="cfTemplateNotForwarded">{0}: Niet doorgestuurd</string>
+  <string name="cfTemplateRegistered">{0}: Niet doorgestuurd ({1})</string>
+  <string name="cfTemplateRegisteredTime">{0}: Niet doorgestuurd ({1} na {2} seconden)</string>
+  <string name="chooseActivity">Een actie selecteren</string>
+  <string name="contentServiceSync">Sync</string>
+  <string name="contentServiceSyncNotificationDesc">Bezig met synchroniseren</string>
+  <string name="contentServiceSyncNotificationTitle">Sync</string>
+  <string name="contentServiceXmppAvailable">XMPP actief</string>
+  <string name="copy">Kopiëren</string>
+  <string name="copyUrl">URL kopiëren</string>
+  <string name="cut">Knippen</string>
+  <string name="daily">Elke dag</string>
+  <string name="daily_format">h:mm aa</string>
+  <string name="date1_date2">"<xliff:g id="format">%2$s \u2013 %5$s</xliff:g>"</string>
+  <string name="date1_time1_date2_time2">"<xliff:g id="format">%2$s, %3$s \u2013 %5$s, %6$s</xliff:g>"</string>
+  <string name="date_picker_set">Instellen</string>
+  <string name="date_range_separator">" \u2013 "</string>
+  <string name="day">dag</string>
+  <string name="day_of_week_long_friday">Vrijdag</string>
+  <string name="day_of_week_long_monday">Maandag</string>
+  <string name="day_of_week_long_saturday">Zaterdag</string>
+  <string name="day_of_week_long_sunday">Zondag</string>
+  <string name="day_of_week_long_thursday">Donderdag</string>
+  <string name="day_of_week_long_tuesday">Dinsdag</string>
+  <string name="day_of_week_long_wednesday">Woensdag</string>
+  <string name="day_of_week_medium_friday">Vri</string>
+  <string name="day_of_week_medium_monday">Maa</string>
+  <string name="day_of_week_medium_saturday">Zat</string>
+  <string name="day_of_week_medium_sunday">Zon</string>
+  <string name="day_of_week_medium_thursday">Don</string>
+  <string name="day_of_week_medium_tuesday">Din</string>
+  <string name="day_of_week_medium_wednesday">Woe</string>
+  <string name="day_of_week_short_friday">Vr</string>
+  <string name="day_of_week_short_monday">Ma</string>
+  <string name="day_of_week_short_saturday">Za</string>
+  <string name="day_of_week_short_sunday">Zo</string>
+  <string name="day_of_week_short_thursday">Do</string>
+  <string name="day_of_week_short_tuesday">Di</string>
+  <string name="day_of_week_short_wednesday">Wo</string>
+  <string name="day_of_week_shorter_friday">V</string>
+  <string name="day_of_week_shorter_monday">M</string>
+  <string name="day_of_week_shorter_saturday">Za</string>
+  <string name="day_of_week_shorter_sunday">Zo</string>
+  <string name="day_of_week_shorter_thursday">Do</string>
+  <string name="day_of_week_shorter_tuesday">Di</string>
+  <string name="day_of_week_shorter_wednesday">W</string>
+  <string name="day_of_week_shortest_friday">V</string>
+  <string name="day_of_week_shortest_monday">M</string>
+  <string name="day_of_week_shortest_saturday">Z</string>
+  <string name="day_of_week_shortest_sunday">Z</string>
+  <string name="day_of_week_shortest_thursday">Do</string>
+  <string name="day_of_week_shortest_tuesday">Di</string>
+  <string name="day_of_week_shortest_wednesday">W</string>
+  <string name="days">dagen</string>
+  <string name="daysDurationFuturePlural">over <xliff:g id="days">%d</xliff:g> dagen</string>
+  <string name="daysDurationPastPlural"><xliff:g id="days">%d</xliff:g> dagen geleden</string>
+  <string name="defaultMsisdnAlphaTag">Msisdn1</string>
+  <string name="defaultVoiceMailAlphaTag">Voicemail</string>
+  <string name="elapsed_time_short_format_h_mm_ss"><xliff:g id="format">%1$d:%2$02d:%3$02d</xliff:g></string>
+  <string name="elapsed_time_short_format_mm_ss"><xliff:g id="format">%1$02d:%2$02d</xliff:g></string>
+  <string name="ellipsis">\u2026</string>
+  <string name="emergency_call_dialog_call">Noodoproep</string>
+  <string name="emergency_call_dialog_cancel">Annuleren</string>
+  <string name="emergency_call_dialog_number_for_display">Alarmnummers</string>
+  <string name="emergency_call_dialog_text">Een alarmnummer bellen?</string>
+  <string name="emergency_call_number_uri">tel:112</string>
+  <string name="emptyPhoneNumber">(geen telefoonnummer)</string>
+  <string name="every_weekday">"Elke werkdag (Maa\u2013vri)"</string>
+  <string name="factorytest_failed">Fabriekstest mislukt</string>
+  <string name="factorytest_no_action">Geen pakket gevonden dat de handeling        FACTORY_TEST levert.</string>
+  <string name="factorytest_not_system">De handeling FACTORY_TEST
+        wordt alleen ondersteund voor pakketten die geïnstalleerd zijn in /system/app.</string>
+  <string name="factorytest_reboot">Opnieuw opstarten</string>
+  <string name="friday">Vrijdag</string>
+  <string name="gigabyteShort">Gb</string>
+  <string name="global_action_lock">Vergrendeling</string>
+  <string name="global_action_power_off">Uitschakelen</string>
+  <string name="global_action_silent_mode_off_status">Geluid staat AAN</string>
+  <string name="global_action_silent_mode_on_status">Geluid staat UIT</string>
+  <string name="global_action_toggle_silent_mode">Stille modus</string>
+  <string name="global_actions">Globale handelingen</string>
+  <string name="hour">uur</string>
+  <string name="hours">uur</string>
+  <string name="httpError">Onbekende fout</string>
+  <string name="httpErrorAuth">Verificatie mislukt</string>
+  <string name="httpErrorBadUrl">Ongeldige url</string>
+  <string name="httpErrorConnect">Verbinding maken met server mislukt</string>
+  <string name="httpErrorFailedSslHandshake">Uitvoeren van ssl-handshake mislukt</string>
+  <string name="httpErrorFile">Bestandfout</string>
+  <string name="httpErrorFileNotFound">Bestand niet gevonden</string>
+  <string name="httpErrorIO">Lezen van of schrijven naar server mislukt</string>
+  <string name="httpErrorLookup">Onbekende host</string>
+  <string name="httpErrorOk">OK</string>
+  <string name="httpErrorProxyAuth">Verificeren van proxyserver mislukt</string>
+  <string name="httpErrorRedirectLoop">Te veel omleidingen door server</string>
+  <string name="httpErrorTimeout">Time-out bij serververbinding</string>
+  <string name="httpErrorUnsupportedAuthScheme">Niet-ondersteund verificatieschema. Verificeren mislukt.</string>
+  <string name="httpErrorUnsupportedScheme">Protocol wordt niet ondersteund</string>
+  <string name="in">in</string>
+  <string name="keyguard_label_text">Om vrij te geven drukt u op Menu en vervolgens op 0.</string>
+  <string name="keyguard_password_emergency_instructions">Druk op de Beltoets om het alarmnummer te bellen.</string>
+  <string name="keyguard_password_enter_pin_code">Pincode invoeren</string>
+  <string name="keyguard_password_instructions">Voer een wachtwoord in of bel een alarmnummer.</string>
+  <string name="keyguard_password_wrong_pin_code">Onjuiste pincode!</string>
+  <string name="kilobyteShort">Kb</string>
+  <string name="lockscreen_carrier_default">(Geen service)</string>
+  <string name="lockscreen_carrier_key">gsm.operator.alpha</string>
+  <string name="lockscreen_emergency_call">Noodoproep</string>
+  <string name="lockscreen_instructions_when_pattern_disabled">Druk op Menu om vrij te geven</string>
+  <string name="lockscreen_instructions_when_pattern_enabled">Druk op Menu om vrij te geven of bel een alarmnummer</string>
+  <string name="lockscreen_low_battery">Oplader verbinden</string>
+  <string name="lockscreen_missing_sim_instructions">Voer een SIM-kaart in</string>
+  <string name="lockscreen_missing_sim_message">Geen SIM in toestel</string>
+  <string name="lockscreen_pattern_correct">Juist!</string>
+  <string name="lockscreen_pattern_instructions">Patroon tekenen om vrij te geven</string>
+  <string name="lockscreen_pattern_wrong">Verkeerd patroon! Nogmaals proberen</string>
+  <string name="lockscreen_plugged_in">Bezig met opladen (<xliff:g id="number">%d%%</xliff:g>)</string>
+  <string name="lockscreen_screen_locked">Schermblokkering</string>
+  <string name="lockscreen_too_many_failed_attempts_countdown">Probeer opnieuw over <xliff:g id="number">%d</xliff:g> seconden</string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_message">
+        U heeft nu <xliff:g id="number">%d</xliff:g> mislukte pogingen om
+        het vrijgavepatroon correct te tekenen.\n
+        Probeer opnieuw over <xliff:g id="number">%d</xliff:g> seconden.
+    </string>
+  <string name="lockscreen_too_many_failed_attempts_dialog_title">Blokkeerpatroonwaarschuwing</string>
+  <string name="low_internal_storage_text">Weinig intern geheugen</string>
+  <string name="low_internal_storage_view_text">Toestel heeft weinig intern geheugen</string>
+  <string name="low_internal_storage_view_title">Weinig intern geheugen</string>
+  <string name="megabyteShort">Mb</string>
+  <string name="midnight">"middernacht"</string>
+  <string name="minute">minuut</string>
+  <string name="minutes">minuten</string>
+  <string name="mmiComplete">MMI voltooid</string>
+  <string name="mmiError">Netwerkfout of onjuiste MMI-code.</string>
+  <string name="monday">Maandag</string>
+  <string name="month">"<xliff:g id="format">%B</xliff:g>"</string>
+  <string name="month_day">"<xliff:g id="format">%-d %B</xliff:g>"</string>
+  <string name="month_day_year">"<xliff:g id="format">%-d %B %Y</xliff:g>"</string>
+  <string name="month_long_april">April</string>
+  <string name="month_long_august">Augustus</string>
+  <string name="month_long_december">December</string>
+  <string name="month_long_february">Februari</string>
+  <string name="month_long_january">Januari</string>
+  <string name="month_long_july">Juli</string>
+  <string name="month_long_june">Juni</string>
+  <string name="month_long_march">Maart</string>
+  <string name="month_long_may">Mei</string>
+  <string name="month_long_november">November</string>
+  <string name="month_long_october">Oktober</string>
+  <string name="month_long_september">September</string>
+  <string name="month_medium_april">apr</string>
+  <string name="month_medium_august">aug</string>
+  <string name="month_medium_december">dec</string>
+  <string name="month_medium_february">feb</string>
+  <string name="month_medium_january">jan</string>
+  <string name="month_medium_july">jul</string>
+  <string name="month_medium_june">jun</string>
+  <string name="month_medium_march">mrt</string>
+  <string name="month_medium_may">mei</string>
+  <string name="month_medium_november">nov</string>
+  <string name="month_medium_october">okt</string>
+  <string name="month_medium_september">sep</string>
+  <string name="month_shortest_april">A</string>
+  <string name="month_shortest_august">A</string>
+  <string name="month_shortest_december">D</string>
+  <string name="month_shortest_february">F</string>
+  <string name="month_shortest_january">J</string>
+  <string name="month_shortest_july">J</string>
+  <string name="month_shortest_june">J</string>
+  <string name="month_shortest_march">M</string>
+  <string name="month_shortest_may">M</string>
+  <string name="month_shortest_november">N</string>
+  <string name="month_shortest_october">O</string>
+  <string name="month_shortest_september">S</string>
+  <string name="month_year">"<xliff:g id="format">%B %Y</xliff:g>"</string>
+  <string name="monthly">Elke mnd</string>
+  <string name="monthly_format">d MMM</string>
+  <string name="more_item_label">Meer</string>
+  <string name="no">Annuleren</string>
+  <string name="noApplications">Geen toepassingen beschikbaar om        de handeling uit te voeren</string>
+  <string name="no_recent_tasks">Geen recente toepassingen</string>
+  <string name="noon">"12 uur 'smiddags"</string>
+  <string name="numeric_date">"<xliff:g id="format">%d/%m/%Y</xliff:g>"</string>
+  <string name="numeric_date_notation">"<xliff:g id="format">%d/%m/%y</xliff:g>"</string>
+  <string name="numeric_md1_md2">"<xliff:g id="format">%3$s/%2$s \u2013 %8$s/%7$s</xliff:g>"</string>
+  <string name="numeric_md1_time1_md2_time2">"<xliff:g id="format">%3$s/%2$s, %5$s \u2013 %8$s/%7$s, %10$s</xliff:g>"</string>
+  <string name="numeric_mdy1_mdy2">"<xliff:g id="format">%3$s/%2$s/%4$s \u2013 %8$s/%7$s/%9$s</xliff:g>"</string>
+  <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s/%2$s/%4$s, %5$s \u2013 %8$s/%7$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s %3$s/%2$s, %5$s \u2013 %6$s %8$s/%7$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s %3$s/%2$s \u2013 %6$s %8$s/%7$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s %3$s/%2$s/%4$s, %5$s \u2013 %6$s %8$s/%7$s/%9$s, %10$s</xliff:g>"</string>
+  <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s %3$s/%2$s/%4$s \u2013 %6$s %8$s/%7$s/%9$s</xliff:g>"</string>
+  <string name="ok">OK</string>
+  <string name="open_permission_deny">U hebt geen toestemming om deze pagina te openen.</string>
+  <string name="passwordIncorrect">Ongeldig wachtwoord</string>
+  <string name="paste">Plakken</string>
+  <string name="permdesc_accessFineLocation">Open, indien beschikbaar, het Global
+        Positioning System op het toestel.
+        Verkeerde toepassingen kunnen dit gebruiken om te bepalen waar u
+        zich bevindt en extra batterijstroom verbruiken.</string>
+  <string name="permdesc_accessCoarseLocation">Gebruik, indien beschikbaar, een
+        netwerkdatabank om de locatie van het toestel te schatten. Verkeerde toepassingen
+        kunnen dit gebruiken om te schatten waar u zich bevindt.</string>
+  <string name="permdesc_accessPhoneInterface">Toepassingen mogen de interne
+        Telefooninterface openen. Is bij normale toepassingen nooit nodig.</string>
+  <string name="permdesc_accessSurfaceFlinger">Toepassingen mogen de        functies op laag niveau van SurfaceFlinger gebruiken.</string>
+  <string name="permdesc_addSystemService">Toepassingen mogen hun
+        eigen systeemdiensten van laag-niveau publiceren. Verkeerde toepassingen kunnen
+        het systeem kapen en gegevens erop beschadigen of stelen.</string>
+  <string name="permdesc_brick">Hiermee kan de toepassing de gehele
+        dienst permanent uitschakelen. Dit is erg gevaarlijk.</string>
+  <string name="permdesc_broadcastPackageRemoved">Hiermee kan een toepassing
+        een melding uitzenden met de mededeling dat een toepassingspakket is verwijderd.
+        Verkeerde toepassingen kunnen dit gebruiken om andere
+        actieve toepassingen te stoppen.</string>
+  <string name="permdesc_broadcastSticky">Hiermee kan een toepassing
+        plakuitzendingen versturen, die blijven hangen als de uitzending stopt.
+        Verkeerde toepassingen kunnen het toestel traag of instabiel maken
+        door te veel geheugengebruik te veroorzaken.</string>
+  <string name="permdesc_callPhone">Hiermee kunnen toepassingen
+        telefoonnummers bellen zonder uw tussenkomst. Verkeerde toepassingen
+        kunnen ongewilde gesprekken op uw telefoonrekening veroorzaken.</string>
+  <string name="permdesc_camera">Hiermee kunnen toepassingen
+        foto's maken met de camera. Hiermee kan de toepassing op elk moment de
+        beelden van de camera ophalen.</string>
+  <string name="permdesc_changeComponentState">Hiermee kan een toepassing veranderen of
+        een component van een andere toepassing wordt ingeschakeld of niet. Slechte toepassingen kunnen
+        dit gebruiken om belangrijke toestelfuncties uit te schakelen. Wees voorzichtig met het verlenen van
+        toestemmingen, omdat het mogelijk is dat toepassingscomponenten in een onbruikbare, inconsistente of instabiele toestand geraken.
+    </string>
+  <string name="permdesc_changeConfiguration">Hiermee kan een toepassing
+        de actuele configuratie veranderen, zoals de locatie of algemene
+        tekengrootte.</string>
+  <string name="permdesc_clearAppCache">Hiermee kan een toepassing toestelgeheugen
+        vrijmaken door bestanden uit de cachemap te verwijderen. Toegang wordt 
+        meestal beperkt tot systeemprocessen.</string>
+  <string name="permdesc_deleteCacheFiles">Hiermee kan een toepassing        cachebestanden verwijderen.</string>
+  <string name="permdesc_deletePackages">Hiermee kan een toepassing
+        Android-pakketten verwijderen. Slechte toepassingen kunnen dit misbruiken om belangrijke toepassingen te wissen.</string>
+  <string name="permdesc_devicePower">Hiermee kan de toepassing het        toestel in- of uitschakelen, of ingeschakeld laten.</string>
+  <string name="permdesc_dump">Hiermee kan een toepassingen
+        de interne toestand van het systeem opvragen. Verkeerde toepassingen kunnen
+        een diverse privé- en veiligheidsinformatie verkrijgen die ze
+        normaal gesproken niet nodig hebben.</string>
+  <string name="permdesc_factoryTest">Uitvoeren als een fabriekstest op laag
+        niveau, waardoor volledige toegang tot de toestelhardware gegeven is. Alleen beschikbaar
+        als een toestel in de fabriekstestmodus staat.</string>
+  <string name="permdesc_flashlight">Hiermee kan de toepassing de        flitser besturen.</string>
+  <string name="permdesc_forceBack">Hiermee kan een toepassing elke
+        activiteit die op de voorgrond staat sluiten en terugkeren.
+        Is bij normale toepassingen nooit nodig.</string>
+  <string name="permdesc_fotaUpdate">Hiermee kan een toepassing
+        meldingen ontvangen over systeemupdates die in behandeling zijn en hun installatie
+        in gang zetten. Slechte toepassingen kunnen dit misbruiken om het
+        te beschadigen met niet toegestane updates, of algemeen het updaten
+        te verstoren.</string>
+  <string name="permdesc_getTasks">Hiermee kan een toepassing
+        informatie ophalen over geactiveerde en recent geactiveerde taken. Verkeerde
+        toepassingen kunnen hierdoor
+        toegang krijgen tot privé-informatie over andere toepassingen.</string>
+  <string name="permdesc_hardware_test">Hiermee kan de toepassing diverse        randapparaten besturen met als doel het testen van de hardware.</string>
+  <string name="permdesc_injectEvents">Hiermee kan een toepassing
+        zijn eigen invoergebeurtenissen (toetsindrukken enz.) naar andere toepassingen sturen. Slechte
+        toepassingen kunnen dit misbruiken om controle over het toestel te krijgen.</string>
+  <string name="permdesc_installPackages">Hiermee kan een toepassing nieuwe Android-
+        pakketten installeren of updaten. Slechte toepassingen kunnen dit misbruiken om nieuwe
+        toepassingen met twijfelachtig verstrekkende rechten toe te voegen.</string>
+  <string name="permdesc_internalSystemWindow">Hiermee kunnen vensters
+        worden gemaakt die bedoeld zijn voor gebruik door het interne systeem
+        van de gebruikersinterface. Niet bedoeld voor normale toepassingen.</string>
+  <string name="permdesc_manageAppTokens">Hiermee kunnen toepassingen
+        hun eigen tokens maken, en zo de normale Z-ordening
+        buiten spel zetten. Is bij normale toepassingen nooit nodig.</string>
+  <string name="permdesc_masterClear">Hiermee kan een toepassing het
+        systeem volledig opnieuw instellen op fabrieksinstellingen, waardoor alle gegevens,
+        configuratie en geïnstalleerde toepassingen worden gewist.</string>
+  <string name="permdesc_modifyAudioSettings">Hiermee kan een toepassing        globale audioinstellingen aanpassen, zoals volume en route.</string>
+  <string name="permdesc_mount_unmount_filesystems">Hiermee kan de toepassing        bestandssystemen voor verwisselbaar geheugen koppelen en loskoppelen.</string>
+  <string name="permdesc_persistentActivity">Hiermee kan een toepassing
+        gedeeltes van zichzelf fixeren, zodat het systeem deze niet meer kan gebruiken voor andere
+        toepassingen.</string>
+  <string name="permdesc_raisedThreadPriority">Hiermee kan een toepassing
+        zgn raised thread priorities gebruiken, wat mogelijk invloed heeft op de reactiesnelheid van de
+        gebruikersinterface.</string>
+  <string name="permdesc_readContacts">Hiermee kan een toepassing alle
+        contactgegevens (adres) op het toestel lezen. Slechte toepassingen
+        kunnen dit misbruiken om uw gegevens naar andere personen te sturen.</string>
+  <string name="permdesc_readFrameBuffer">Hiermee kan een toepassing        de gegevens van de framebuffer lezen.</string>
+  <string name="permdesc_readInputState">Hiermee kunnen toepassingen de
+        toetsen zien die u indrukt, ook bij interactie met een andere toepassing (zoals
+        het invoeren van een wachtwoord). Is bij normale toepassingen nooit nodig.</string>
+  <string name="permdesc_readSms">Hiermee kan een toepassing
+      sms-berichten op telefoon of SIM-kaart lezen. Slechte toepassingen
+      kunnen vertrouwelijke berichten lezen.</string>
+  <string name="permdesc_receiveBootCompleted">Hiermee kan een toepassing
+        zichzelf starten zodra het systeem is opgestart.
+        Hierdoor kan het opstarten van het toestel langer duren en de
+        toepassing het toestel afremmen omdat het altijd is geactiveerd.</string>
+  <string name="permdesc_receiveMms">Hiermee kan een toepassing
+      multimediaberichten ontvangen en verwerken. Slechte toepassingen kunnen
+      uw berichten in de gaten houden of verwijderen zonder dat dit wordt aangegeven.</string>
+  <string name="permdesc_receiveSms">Hiermee kan een toepassing
+      tekstberichten ontvangen en verwerken. Slechte toepassingen kunnen
+      uw berichten in de gaten houden of verwijderen zonder dat dit wordt aangegeven.</string>
+  <string name="permdesc_receiveWapPush">Hiermee kan een toepassing
+      wap-berichten ontvangen en verwerken. Slechte toepassingen kunnen
+      uw berichten in de gaten houden of verwijderen zonder dat dit wordt aangegeven.</string>
+  <string name="permdesc_recordAudio">Hierdoor kan een toepassing        het opnamepad voor audio openen.</string>
+  <string name="permdesc_reorderTasks">Hiermee kan een toepassing
+        taken naar de voorgrond en achtergrond verplaatsen. Slechte toepassingen kunnen
+        zichzelf zonder tussenkomst naar de voorgrond forceren.</string>
+  <string name="permdesc_runInstrumentation">Hiermee kan een toepassing
+        zijn eigen instrumentatiecode invoegen in elke andere toepassing.
+        Slechte toepassingen kunnen zo het systeem in gevaar brengen. Deze
+        toepassing is alleen nodig voor ontwikkelingsdoeleinden, nooit voor normaal
+        gebruik van het toestel.</string>
+  <string name="permdesc_runSetActivityWatcher">Hiermee kan een toepassing
+        de wijze waarop het systeem activiteiten start in de gaten houden en besturen.
+        Slechte toepassingen kunnen zo het systeem in gevaar brengen. Deze
+        toepassing is alleen nodig voor ontwikkelingsdoeleinden, nooit voor normaal
+        gebruik van het toestel.</string>
+  <string name="permdesc_sendSms">Hiermee kan een toepassing
+      tekstberichten verzenden. Slechte toepassingen kunnen u op kosten jagen door
+      zonder toestemming berichten te verzenden.</string>
+  <string name="permdesc_setAlwaysFinish">Hiermee kan een toepassing
+        besturen of activiteiten altijd voltooid moeten zijn als ze
+        naar de achtergrond gaan. Nooit
+        nodig voor normale toepassingen.</string>
+  <string name="permdesc_setAnimationScale">Hiermee kan een toepassing op        elk moment de globale animatiesnelheid (snellere of tragere animaties) veranderen.</string>
+  <string name="permdesc_setDebugApp">Hiermee kan een toepassing
+        foutopsporing voor andere toepassingen inschakelen. Slechte toepassingen kunnen
+        dit misbruiken om andere toepassingen te stoppen.</string>
+  <string name="permdesc_setOrientation">Hiermee kan een toepassing op
+        elk moment de schermligging veranderen. Is bij normale toepassingen
+        nooit nodig.</string>
+  <string name="permdesc_setPreferredApplications">Hiermee kan een toepassing
+        uw voorkeurstoepassingen veranderen. Slechte toepassingen kunnen zo
+        stilletjes veranderen welke toepassingen gestart moeten worden, zodat
+        bestaande toepassingen privégegevens over u verzamelen.</string>
+  <string name="permdesc_setProcessLimit">Hiermee kan een toepassing
+        het maximumaantal geactiveerde processen besturen. Nooit
+        nodig voor normale toepassingen.</string>
+  <string name="permdesc_setWallpaper">Hiermee kan de toepassing        de systeemachtergrond instellen.</string>
+  <string name="permdesc_signalPersistentProcesses">Hiermee kan een toepassing verzoeken        dat het meegeleverde signaal naar alle aanhoudende processen wordt gestuurd.</string>
+  <string name="permdesc_statusBar">Hiermee kan een toepassing        de statusbalk en pictogrammen openen, sluiten of uitschakelen.</string>
+  <string name="permdesc_systemAlertWindow">Hiermee kan een toepassing
+        systeemmeldingen weergeven. Slechte toepassingen kunnen het
+        volledige toestelscherm overnemen.</string>
+  <string name="permdesc_vibrate">Hiermee kan de toepassing de        triller besturen.</string>
+  <string name="permdesc_writeContacts">Hiermee kan een toepassing alle
+        contactgegevens (adres) op het toestel aanpassen. Slechte
+        toepassingen kunnen dit misbruiken om contactgegevens te wissen of wijzigen.</string>
+  <string name="permdesc_writeSettings">Hiermee kan een toepassing de
+        instellingsgegevens van het systeem aanpassen. Slechte toepassingen kunnen de configuratie
+        van het systeem beschadigen.</string>
+  <string name="permdesc_writeSms">Hiermee kan een toepassing
+      sms-berichten op telefoon of SIM-kaart schrijven. Slechte toepassingen
+      kunnen berichten verwijderen.</string>
+  <string name="permlab_accessFineLocation">Toegang tot gps-locatie</string>
+  <string name="permlab_accessCoarseLocation">Toegang tot netwerklocatie</string>
+  <string name="permlab_accessPhoneInterface">Toegang tot telefooninterface</string>
+  <string name="permlab_accessSurfaceFlinger">Toegang tot SurfaceFlinger</string>
+  <string name="permlab_addSystemService">Systeemservice toevoegen</string>
+  <string name="permlab_brick">Toestel uitschakelen</string>
+  <string name="permlab_broadcastPackageRemoved">Uitzendpakket verwijderd</string>
+  <string name="permlab_broadcastSticky">Belangrijke uitzending</string>
+  <string name="permlab_callPhone">Telefoonnummers bellen</string>
+  <string name="permlab_camera">Camera</string>
+  <string name="permlab_changeComponentState">Toepassingscomponenten in- of uitschakelen</string>
+  <string name="permlab_changeConfiguration">Configuratie wijzigen</string>
+  <string name="permlab_clearAppCache">Cachegegevens van toepassing wissen</string>
+  <string name="permlab_deleteCacheFiles">Cachebestanden wissen</string>
+  <string name="permlab_deletePackages">Pakketten verwijderen</string>
+  <string name="permlab_devicePower">Toestel inschakelen</string>
+  <string name="permlab_dump">Systeemstatus dumpen</string>
+  <string name="permlab_factoryTest">Fabriekstest</string>
+  <string name="permlab_flashlight">Flitser</string>
+  <string name="permlab_forceBack">Terug forceren</string>
+  <string name="permlab_fotaUpdate">Installatie van systeemupdate</string>
+  <string name="permlab_getTasks">Taakinformatie verkrijgen</string>
+  <string name="permlab_hardware_test">Hardwaretest</string>
+  <string name="permlab_injectEvents">Invoergebeurtenissen invoegen</string>
+  <string name="permlab_installPackages">Pakketten installeren</string>
+  <string name="permlab_internalSystemWindow">Intern systeemvenster</string>
+  <string name="permlab_manageAppTokens">Toepassingstokens beheren</string>
+  <string name="permlab_masterClear">Volledige systeemreset</string>
+  <string name="permlab_modifyAudioSettings">Audioinstellingen wijzigen</string>
+  <string name="permlab_mount_unmount_filesystems">Bestandssystemen koppelen en losmaken</string>
+  <string name="permlab_persistentActivity">Aanhoudende handelingen</string>
+  <string name="permlab_raisedThreadPriority">Raised thread priorities</string>
+  <string name="permlab_readContacts">Contactgegevens lezen</string>
+  <string name="permlab_readFrameBuffer">Framebuffer lezen</string>
+  <string name="permlab_readInputState">Invoerstatus lezen</string>
+  <string name="permlab_readSms">Sms/mms-berichten lezen</string>
+  <string name="permlab_receiveBootCompleted">Uitvoeren bij opstarten</string>
+  <string name="permlab_receiveMms">Mms-berichten ontvangen</string>
+  <string name="permlab_receiveSms">Sms-berichten ontvangen</string>
+  <string name="permlab_receiveWapPush">Wap-berichten ontvangen</string>
+  <string name="permlab_recordAudio">Audio opnemen</string>
+  <string name="permlab_reorderTasks">Taken herschikken</string>
+  <string name="permlab_runInstrumentation">Instrumentatie uitvoeren</string>
+  <string name="permlab_runSetActivityWatcher">Activiteitenmonitor instellen</string>
+  <string name="permlab_sendSms">Sms-berichten verzenden</string>
+  <string name="permlab_setAlwaysFinish">Altijd voltooien instellen</string>
+  <string name="permlab_setAnimationScale">Animatieschaal instellen</string>
+  <string name="permlab_setDebugApp">Foutopsporing instellen</string>
+  <string name="permlab_setOrientation">Ligging instellen</string>
+  <string name="permlab_setPreferredApplications">Voorkeurstoepassingen instellen</string>
+  <string name="permlab_setProcessLimit">Proceslimiet instellen</string>
+  <string name="permlab_setWallpaper">Achtergrond instellen</string>
+  <string name="permlab_signalPersistentProcesses">Signaal naar aanhoudende processen</string>
+  <string name="permlab_statusBar">Statusbalk besturen</string>
+  <string name="permlab_systemAlertWindow">Systeemmeldingen</string>
+  <string name="permlab_vibrate">Triller</string>
+  <string name="permlab_writeContacts">Contactgegevens schrijven</string>
+  <string name="permlab_writeSettings">Systeeminstellingen schrijven</string>
+  <string name="permlab_writeSms">Sms/mms-berichten schrijven</string>
+  <string name="petabyteShort">Pb</string>
+  <string name="pm">"PM"</string>
+  <string name="power_dialog">Energieopties</string>
+  <string name="power_off">Uitschakelen</string>
+  <string name="prepend_shortcut_label">Menu+</string>
+  <string name="preposition_for_date">op %s</string>
+  <string name="preposition_for_time">bij %s</string>
+  <string name="preposition_for_year">in %s</string>
+  <string name="safeMode">Veilige modus</string>
+  <string name="same_month_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s</xliff:g>"</string>
+  <string name="same_month_md1_time1_md2_time2">"<xliff:g id="format">%3$s %2$s, %5$s \u2013 %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_month_mdy1_mdy2">"<xliff:g id="format">%3$s \u2013 %8$s %2$s, %9$s</xliff:g>"</string>
+  <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s %2$s %4$s, %5$s \u2013 %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s %3$s %2$s, %5$s \u2013 %6$s %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s %3$s %2$s \u2013 %6$s %8$s %7$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s %3$s %2$s %4$s, %5$s \u2013 %6$s %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s %3$s %2$s %4$s \u2013 %6$s %8$s %7$s, %9$s</xliff:g>"</string>
+  <string name="same_year_md1_md2">"<xliff:g id="format">%3$s %2$s \u2013 %8$s %7$s</xliff:g>"</string>
+  <string name="same_year_md1_time1_md2_time2">"<xliff:g id="format">%3$s %2$s, %5$s \u2013 %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_year_mdy1_mdy2">"<xliff:g id="format">%3$s %2$s \u2013 %8$s %7$s, %9$s</xliff:g>"</string>
+  <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="format">%3$s %2$s %4$s, %5$s \u2013 %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s %3$s %2$s, %5$s \u2013 %6$s %8$s %7$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s %3$s %2$s \u2013 %6$s %8$s %7$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s %3$s %2$s %4$s, %5$s \u2013 %6$s %8$s %7$s %9$s, %10$s</xliff:g>"</string>
+  <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s %3$s %2$s \u2013 %6$s %8$s %7$s %9$s</xliff:g>"</string>
+  <string name="saturday">Zaterdag</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_never">Nooit</string>
+  <string name="save_password_notnow">Niet nu</string>
+  <string name="save_password_remember">Onthouden</string>
+  <string name="screen_lock">Vergrendeling</string>
+  <string name="screen_progress">Bezig\u2026</string>
+  <string name="search_go">Ga naar</string>
+  <string name="second">seconde</string>
+  <string name="seconds">seconden</string>
+  <string name="selectAll">Alles selecteren</string>
+  <string name="selectMenuLabel">Selecteren</string>
+  <string name="sendText">Kiezen wat met de tekst gebeurt</string>
+  <string name="serviceClassData">Gegevens</string>
+  <string name="serviceClassDataAsync">Async</string>
+  <string name="serviceClassDataSync">Sync</string>
+  <string name="serviceClassFAX">FAX</string>
+  <string name="serviceClassPAD">PAD</string>
+  <string name="serviceClassPacket">Pakket</string>
+  <string name="serviceClassSMS">SMS</string>
+  <string name="serviceClassVoice">Spraak</string>
+  <string name="serviceDisabled">Service uitgeschakeld</string>
+  <string name="serviceEnabled">Service ingeschakeld</string>
+  <string name="serviceEnabledFor">Service ingeschakeld voor:</string>
+  <string name="serviceErased">Wissen gelukt</string>
+  <string name="serviceNotProvisioned">Service niet opgenomen.</string>
+  <string name="serviceRegistered">Registratie gelukt</string>
+  <string name="silent_mode">Stille modus</string>
+  <string name="simAbsentLabel">SIM ontbreekt of onjuist geplaatst</string>
+  <string name="simNetworkPersonalizationLabel">SIM kan niet op dit toestel worden gebruikt</string>
+  <string name="simPINLabel">SIM-pincode nodig (en momenteel niet ondersteund)</string>
+  <string name="simPUKLabel">SIM-PUK-code nodig (en momenteel niet ondersteund)</string>
+  <string name="status_bar_applications_title">Toepassing</string>
+  <string name="status_bar_date_format">"<xliff:g id="format">d MMMM yyyy</xliff:g>"</string>
+  <string name="status_bar_latest_events_title">Nieuwste gebeurtenissen</string>
+  <string name="status_bar_no_notifications_title">Meldingen</string>
+  <string name="status_bar_ongoing_events_title">Actueel</string>
+  <string name="status_bar_time_format">"<xliff:g id="format">h:mm AA</xliff:g>"</string>
+  <string name="sunday">Zondag</string>
+  <string name="terabyteShort">Tb</string>
+  <string name="thursday">Donderdag</string>
+  <string name="time1_time2">"<xliff:g id="format">%1$s \u2013 %2$s</xliff:g>"</string>
+  <string name="time_date">"<xliff:g id="format">%1$s, %3$s</xliff:g>"</string>
+  <string name="time_picker_set">Instellen</string>
+  <string name="time_wday">"<xliff:g id="format">%2$s %1$s</xliff:g>"</string>
+  <string name="time_wday_date">"<xliff:g id="format">%2$s %1$s %3$s</xliff:g>"</string>
+  <string name="today">Vandaag</string>
+  <string name="tomorrow">Morgen</string>
+  <string name="tuesday">Dinsdag</string>
+  <string name="turn_off_radio">Radio uitschakelen</string>
+  <string name="turn_on_radio">Radio uitschakelen</string>
+  <string name="unknownName">(onbekend)</string>
+  <string name="untitled">&lt;Naamloos&gt;</string>
+  <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="format">%1$s %2$s, %3$s \u2013 %4$s %5$s, %6$s</xliff:g>"</string>
+  <string name="wday1_date1_wday2_date2">"<xliff:g id="format">%1$s %2$s \u2013 %4$s %5$s</xliff:g>"</string>
+  <string name="wday_date">"<xliff:g id="format">%2$s %3$s</xliff:g>"</string>
+  <string name="web_user_agent">Mozilla/5.0 (Linux; U; Android 0.6; en)
+        AppleWebKit/525.10+ (KHTML, zoals Gecko) Versie/3.0.4 Mobile Safari/523.12.2</string>
+  <string name="wednesday">Woensdag</string>
+  <string name="week">week</string>
+  <string name="weekly">"Wekelijks op <xliff:g id="day">%s</xliff:g>"</string>
+  <string name="weekly_format">d MMM</string>
+  <string name="weeks">weken</string>
+  <string name="whichApplication">Welke toepassing wilt u gebruiken?</string>
+  <string name="yearly">Elk jaar</string>
+  <string name="yearly_format">yyyy</string>
+  <string name="yes">OK</string>
+  <string name="yesterday">Gisteren</string>
+</resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
new file mode 100644
index 0000000..4f40a27
--- /dev/null
+++ b/core/res/res/values/arrays.xml
@@ -0,0 +1,115 @@
+<?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>
+
+    <!-- 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/scrollbar_handle_vertical</item>
+        <item>@drawable/scrollbar_handle_horizontal</item>
+        <item>@drawable/scrollbar_horizontal</item>
+        <item>@drawable/scrollbar_vertical</item>
+        
+        <item>@drawable/edit_text</item>
+        
+        <item>@drawable/title_bar</item>
+        
+        <item>@drawable/spinner_dropdown_background_down</item>
+        <item>@drawable/divider_horizontal_bright</item>
+
+        <item>@drawable/popup_full_dark</item>
+        <item>@drawable/panel_background</item>
+        
+        <item>@drawable/sym_def_app_icon</item>
+        
+        <item>@drawable/progress_horizontal</item>
+        <item>@drawable/list_selector_background</item>
+        <item>@drawable/btn_default</item>
+
+        <!-- New additions... -->
+        <item>@drawable/popup_top_dark</item>
+        <item>@drawable/popup_center_dark</item>
+        <item>@drawable/popup_bottom_dark</item>
+        <item>@drawable/popup_top_bright</item>
+        <item>@drawable/popup_center_bright</item>
+        <item>@drawable/popup_bottom_bright</item>
+        
+        <!-- These are not normally referenced at first boot, but maybe
+             would be good to preload? -->
+        <item>@drawable/btn_check</item>
+        <item>@drawable/btn_dropdown</item>
+        <item>@drawable/btn_radio</item>
+        <item>@drawable/list_selector_background</item>
+        <item>@drawable/btn_default_small</item>
+        <item>@drawable/btn_toggle</item>
+
+        <!-- Contacts -->
+        <item>@drawable/tab_indicator</item>
+        <item>@drawable/btn_star</item>
+        <item>@drawable/button_inset</item>
+        
+        <!-- Menus -->
+        <item>@drawable/menu_background</item>
+        <item>@drawable/menu_background_fill_parent_width</item>
+        <item>@drawable/menu_selector</item>
+        <item>@drawable/divider_vertical_bright</item>
+        
+        <item>@drawable/expander_group</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>
+        
+        <!-- Zoom controls for maps, browser, etc. -->
+        <item>@drawable/btn_plus</item>
+        <item>@drawable/btn_minus</item>
+        <item>@drawable/zoom_plate</item>
+        
+    </array>
+    
+    <integer-array name="maps_starting_lat_lng">
+        <item>36149777</item>
+        <item>-95993398</item>
+    </integer-array>
+    <integer-array name="maps_starting_zoom">
+        <item>3</item>
+    </integer-array>
+
+    <!-- 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>clock</item>
+        <item>alarm_clock</item>
+        <item>battery</item>
+        <item>phone_signal</item>
+        <item>data_connection</item>
+        <item>volume</item>
+        <item>mute</item>
+        <item>speakerphone</item>
+        <item>wifi</item>
+        <item>bluetooth</item>
+        <item>gps</item>
+        <item>sync_active</item>
+        <item>sync_failing</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..6c96cc2
--- /dev/null
+++ b/core/res/res/values/attrs.xml
@@ -0,0 +1,2526 @@
+<?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" />
+
+        <!-- =========== -->
+        <!-- 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" />
+        
+        <!-- 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" />
+    
+        <!-- ============ -->
+        <!-- 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 Spinner style. -->
+        <attr name="spinnerStyle" format="reference" />
+        <!-- Default Star style. -->
+        <attr name="starStyle" 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 (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" />
+    </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" />
+    </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" />
+    </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" />
+    </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="textColor" />
+    </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>
+        
+        <!-- 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" />
+
+    </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>
+
+    <!-- =============================== -->
+    <!-- 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" />
+    </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" />
+        </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" />
+        </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>
+    </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" />
+        <!-- '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. -->
+        <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. -->
+        <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" />
+        <!-- Vertical gravity (top, center_vertical, bottom) 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. -->
+        <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. -->
+        <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. -->
+        <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. -->
+        <attr name="phoneNumber" format="boolean" />
+        <!-- If set, specifies that this TextView should use the specified
+             input method (specified by fully-qualified class name). -->
+        <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". -->
+        <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". -->
+        <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. -->
+        <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" />
+    </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" />        
+    </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="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 attribtues 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 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 top margin. -->
+        <attr name="layout_alignParentRight" format="boolean" />
+        <!-- f true, makes the bottom edge of this view match the bottom edge of the parent.
+            Accommodates top 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>
+        <attr name="innerRadiusRatio" format="float" />
+        <attr name="thicknessRatio" format="float" />
+        <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 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" />
+        </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, the animation transformation is applied before the animation has
+             started. The default value is 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. -->
+        <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 &amp; 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> -->
+        <attr name="searchButtonText" format="string" />
+
+        <!-- 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>
+
+        <!-- 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">
+        <!-- An API Key required to use MapView. Currently, this can be an arbitrary string. 
+             In order to run on actual devices, though, your app will need an authentic key, but
+             the process for issuing keys is not ready at this time. -->
+        <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>
+
+</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..7f47182
--- /dev/null
+++ b/core/res/res/values/attrs_manifest.xml
@@ -0,0 +1,1103 @@
+<?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}devel/security.html">Security Model</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}devel/security.html">Security Model</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}devel/security.html">Security Model</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" />
+    
+    <!-- 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 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}intro/appmodel.html">Application Model</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}intro/appmodel.html">Application Model</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}intro/appmodel.html">Application Model</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 <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 #AndroidManifestApplication application},
+         {@link #AndroidManifestInstrumentation instrumentation}.  -->
+    <declare-styleable name="AndroidManifest">
+        <attr name="versionCode" />
+        <attr name="versionName" />
+        <attr name="sharedUserId" />
+    </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}devel/security.html">Security Model</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}devel/security.html">Security Model</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-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="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" />
+    </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 #IntentExtra <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 in the Intent, as per
+            {@link android.content.Intent#putExtra Intent.putExtra()}. -->
+    <declare-styleable name="IntentExtra" 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..1b80179
--- /dev/null
+++ b/core/res/res/values/colors.xml
@@ -0,0 +1,75 @@
+<?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">#ff191919</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">#ff191919</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>
+    
+    <!-- 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/dimens.xml b/core/res/res/values/dimens.xml
new file mode 100644
index 0000000..2abb26f
--- /dev/null
+++ b/core/res/res/values/dimens.xml
@@ -0,0 +1,29 @@
+<?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>
+</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
new file mode 100644
index 0000000..72e149b
--- /dev/null
+++ b/core/res/res/values/ids.xml
@@ -0,0 +1,53 @@
+<?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" />
+</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
new file mode 100644
index 0000000..9189622
--- /dev/null
+++ b/core/res/res/values/public.xml
@@ -0,0 +1,929 @@
+<!-- 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 />
+
+<!-- ===============================================================
+     Resource set for version 1.0 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>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
new file mode 100644
index 0000000..bec3bc8
--- /dev/null
+++ b/core/res/res/values/strings.xml
@@ -0,0 +1,1787 @@
+<?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">
+    <!-- Abbreviations for different units of information and computer storage -->
+    <!-- used by android.content.Formatter.formatFileSize -->
+    <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="selectMenuLabel">Select</string>
+    <string name="untitled">&lt;untitled&gt;</string>
+    <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>
+
+    <string name="screen_progress">Working\u2026</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 -->
+    <string name="mmiError">Connection problem or invalid MMI code.</string>
+    <string name="serviceEnabled">Service was enabled.</string>
+    <string name="serviceEnabledFor">Service was enabled for:</string>
+    <string name="serviceDisabled">Service has been disabled.</string>
+    <string name="serviceRegistered">Registration was successful.</string>
+    <string name="serviceErased">Erasure was successful.</string>
+    <string name="passwordIncorrect">Incorrect password.</string>
+    <string name="mmiComplete">MMI complete.</string>
+    <string name="badPin">The old PIN you typed is not correct.</string>
+    <string name="badPuk">The PUK you typed is not correct.</string>
+    <string name="mismatchPin">The PINs you entered do not match.</string>
+    <string name="invalidPin">Type a PIN that is 4 to 8 numbers.</string>
+    <string name="needPuk2">Type PUK2 to unblock SIM card.</string>
+
+    <string name="ClipMmi">Incoming Caller ID</string>
+    <string name="ClirMmi">Outgoing Caller ID</string>
+    <string name="CfMmi">Call forwarding</string>
+    <string name="CwMmi">Call waiting</string>
+    <string name="BaMmi">Call barring</string>
+    <string name="PwdMmi">Password change</string>
+    <string name="PinMmi">PIN change</string>
+
+    <string name="CLIRDefaultOnNextCallOn">Caller ID defaults to restricted. Next call: Restricted</string>
+    <string name="CLIRDefaultOnNextCallOff">Caller ID defaults to restricted. Next call: Not restricted</string>
+    <string name="CLIRDefaultOffNextCallOn">Caller ID defaults to not restricted. Next call: Restricted</string>
+    <string name="CLIRDefaultOffNextCallOff">Caller ID defaults to not restricted. Next call: Not restricted</string>
+
+
+    <string name="serviceNotProvisioned">Service not provisioned.</string>
+    <string name="CLIRPermanent">The caller ID setting cannot be changed.</string>
+
+    <!-- Mappings between TS 27.007 +CFCC/+CLCK "service classes" and human-readable strings-->
+    <string name="serviceClassVoice">Voice</string>
+    <string name="serviceClassData">Data</string>
+    <string name="serviceClassFAX">FAX</string>
+    <string name="serviceClassSMS">SMS</string>
+    <string name="serviceClassDataAsync">Async</string>
+    <string name="serviceClassDataSync">Sync</string>
+    <string name="serviceClassPacket">Packet</string>
+    <string name="serviceClassPAD">PAD</string>
+
+    <string name="cfReasonUnconditional">Call forwarding - Always</string>
+    <string name="cfReasonBusy">Call forwarding - Busy</string>
+    <string name="cfReasonNRy">Call forwarding - No reply</string>
+    <string name="cfReasonNR">Call forwarding - Not reachable</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.
+    -->
+    <string name="cfTemplateNotForwarded">{0}: Not forwarded</string>
+    <string name="cfTemplateForwarded">{0}: {1}</string>
+    <string name="cfTemplateForwardedTime">{0}: {1} after {2} seconds</string>
+    <string name="cfTemplateRegistered">{0}: Not forwarded</string>
+    <string name="cfTemplateRegisteredTime">{0}: Not forwarded</string>
+
+    <string name="simAbsentLabel">SIM card absent or incorrectly inserted.</string>
+    <string name="simPINLabel">SIM PIN required (and presently unsupported).</string>
+    <string name="simPUKLabel">SIM PUK required (and presently unsupported).</string>
+    <string name="simNetworkPersonalizationLabel">SIM card cannot be used on this phone.</string>
+
+    <!-- BrowserFrame strings -->
+    <string name="browserSavedFormData">Saved form data.</string>
+
+    <!-- android.net.http Error strings -->
+    <string name="httpErrorOk">OK</string>
+    <string name="httpError">The Web page contains an error.</string>
+    <string name="httpErrorLookup">The URL could not be found.</string>
+    <string name="httpErrorUnsupportedAuthScheme">The site authentication scheme is not supported.</string>
+    <string name="httpErrorAuth">Authentication was unsuccessful.</string>
+    <string name="httpErrorProxyAuth">Authentication via the proxy server was unsuccessful.</string>
+    <string name="httpErrorConnect">The connection to the server was unsuccessful.</string>
+    <string name="httpErrorIO">The server failed to communicate. Try again later.</string>
+    <string name="httpErrorTimeout">The connection to the server timed out.</string>
+    <string name="httpErrorRedirectLoop">The page contains too many server redirects.</string>
+    <string name="httpErrorUnsupportedScheme">The protocol is not supported.</string>
+    <string name="httpErrorFailedSslHandshake">A secure connection could not be established.</string>
+    <string name="httpErrorBadUrl">The page could not be opened because the URL is invalid.</string>
+    <string name="httpErrorFile">The file could not be accessed.</string>
+    <string name="httpErrorFileNotFound">The requested file was not found.</string>
+    <string name="httpErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
+
+    <!-- Sync notifications -->
+    <string name="contentServiceSync">Sync</string>
+    <string name="contentServiceXmppAvailable">XMPP Active</string>
+    <string name="contentServiceSyncNotificationTitle">Sync</string>
+    <string name="contentServiceSyncNotificationDesc">Syncing</string>
+    <string name="contentServiceTooManyDeletesNotificationDesc">Too many %s deletes.</string>
+    <string name="contentServiceSyncErrorNotificationDesc">Sync is experiencing problems.</string>
+
+    <!-- Low memory Toast -->
+    <string name="low_memory">Phone storage is full! Delete some files to free space.</string>
+
+    <!-- Display name of local number -->
+    <string name="me">Me</string>
+
+    <!-- Power Dialog -->
+
+    <string name="power_dialog">Phone options</string>
+    <string name="activate_keyguard">Screen lock</string>
+    <string name="silent_mode">Silent mode</string>
+    <string name="turn_on_radio">Turn on wireless</string>
+    <string name="turn_off_radio">Turn off wireless</string>
+    <string name="screen_lock">Screen lock</string>
+    <string name="power_off">Power off</string>
+
+    <!-- Shutdown Progress Dialog -->
+    <string name="shutdown_progress">Shutting down\u2026</string>
+
+    <!-- Shutdown Confirmation Dialog -->
+    <string name="shutdown_confirm">Your phone will shut down.</string>
+
+    <!-- Recent Tasks dialog -->
+    <string name="no_recent_tasks">No recent applications.</string>
+
+    <!-- Global Actions Dialog -->
+
+    <string name="global_actions">Phone options</string>
+
+    <!-- label for item that locks the phone in the global actions dialog -->
+    <string name="global_action_lock">Screen lock</string>
+
+    <!-- label for item that turns off power in global actions dialog -->
+    <string name="global_action_power_off">Power off</string>
+
+    <!-- label for item that enables silent mode in global actions dialog -->
+    <string name="global_action_toggle_silent_mode">Silent mode</string>
+
+    <!-- status message in global actions dialog for when silent mode is enabled -->
+    <string name="global_action_silent_mode_on_status">Sound is OFF</string>
+
+    <!-- status message in global actions dialog for when silent mode is disabled -->
+    <string name="global_action_silent_mode_off_status">Sound is ON</string>
+
+    <!-- Activity Manager -->
+    <string name="safeMode">Safe mode</string>
+
+    <!--  Permission Groups -->
+
+    <string name="permgrouplab_costMoney">Services that cost you money</string>
+    <string name="permgroupdesc_costMoney">Allow applications to do things
+        that can cost you money.</string>
+
+    <string name="permgrouplab_messages">Your messages</string>
+    <string name="permgroupdesc_messages">Read and write your SMS,
+        e-mail, and other messages.</string>
+
+    <string name="permgrouplab_personalInfo">Your personal information</string>
+    <string name="permgroupdesc_personalInfo">Direct access to your contacts
+        and calendar stored on the phone.</string>
+
+    <string name="permgrouplab_location">Your location</string>
+    <string name="permgroupdesc_location">Monitor your physical location</string>
+
+    <string name="permgrouplab_network">Network communication</string>
+    <string name="permgroupdesc_network">Allow applications to access
+        various network features.</string>
+
+    <string name="permgrouplab_accounts">Your Google accounts</string>
+    <string name="permgroupdesc_accounts">Access the available Google accounts.</string>
+
+    <string name="permgrouplab_hardwareControls">Hardware controls</string>
+    <string name="permgroupdesc_hardwareControls">Direct access to hardware on
+        the handset.</string>
+
+    <string name="permgrouplab_phoneCalls">Phone calls</string>
+    <string name="permgroupdesc_phoneCalls">Monitor, record, and process
+        phone calls.</string>
+
+    <string name="permgrouplab_systemTools">System tools</string>
+    <string name="permgroupdesc_systemTools">Lower-level access and control
+        of the system.</string>
+
+    <string name="permgrouplab_developmentTools">Development tools</string>
+    <string name="permgroupdesc_developmentTools">Features only needed for
+        application developers.</string>
+
+    <!--  Permissions -->
+
+    <string name="permlab_statusBar">disable or modify status bar</string>
+    <string name="permdesc_statusBar">Allows application to disable
+        the status bar or add and remove system icons.</string>
+
+    <string name="permlab_expandStatusBar">expand/collapse status bar</string>
+    <string name="permdesc_expandStatusBar">Allows application to
+        expand or collapse the status bar.</string>
+
+    <string name="permlab_processOutgoingCalls">intercept outgoing calls</string>
+    <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>
+
+    <string name="permlab_receiveSms">receive SMS</string>
+    <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>
+
+    <string name="permlab_receiveMms">receive MMS</string>
+    <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>
+
+    <string name="permlab_sendSms">send SMS messages</string>
+    <string name="permdesc_sendSms">Allows application to send SMS
+      messages. Malicious applications may cost you money by sending
+      messages without your confirmation.</string>
+
+    <string name="permlab_readSms">read SMS or MMS</string>
+    <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>
+
+    <string name="permlab_writeSms">edit SMS or MMS</string>
+    <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>
+
+    <string name="permlab_receiveWapPush">receive WAP</string>
+    <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>
+
+    <string name="permlab_getTasks">retrieve running applications</string>
+    <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>
+
+    <string name="permlab_reorderTasks">reorder running applications</string>
+    <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>
+
+    <string name="permlab_setDebugApp">enable application debugging</string>
+    <string name="permdesc_setDebugApp">Allows an application to turn
+        on debugging for another application. Malicious applications can use this
+        to kill other applications.</string>
+
+    <string name="permlab_changeConfiguration">change your UI settings</string>
+    <string name="permdesc_changeConfiguration">Allows an application to
+        change the current configuration, such as the locale or overall font
+        size.</string>
+
+    <string name="permlab_restartPackages">restart other applications</string>
+    <string name="permdesc_restartPackages">Allows an application to
+        forcibly restart other applications.</string>
+
+    <string name="permlab_setProcessForeground">keep from being stopped</string>
+    <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>
+
+    <string name="permlab_forceBack">force application to close</string>
+    <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>
+
+    <string name="permlab_dump">retrieve system internal state</string>
+    <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>
+
+    <string name="permlab_addSystemService">publish low-level services</string>
+    <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>
+
+    <string name="permlab_runSetActivityWatcher">monitor and control all application launching</string>
+    <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>
+
+    <string name="permlab_broadcastPackageRemoved">send package removed broadcast</string>
+    <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>
+
+    <string name="permlab_setProcessLimit">limit number of running processes</string>
+    <string name="permdesc_setProcessLimit">Allows an application
+        to control the maximum number of processes that will run. Never
+        needed for normal applications.</string>
+
+    <string name="permlab_setAlwaysFinish">make all background applications close</string>
+    <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>
+
+    <string name="permlab_fotaUpdate">automatically install system updates</string>
+    <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>
+
+    <string name="permlab_batteryStats">modify battery statistics</string>
+    <string name="permdesc_batteryStats">Allows the modification of
+        collected battery statistics. Not for use by normal applications.</string>
+
+    <string name="permlab_internalSystemWindow">display unauthorized windows</string>
+    <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>
+
+    <string name="permlab_systemAlertWindow">display system-level alerts</string>
+    <string name="permdesc_systemAlertWindow">Allows an application to
+        show system alert windows. Malicious applications can take over the
+        entire screen of the phone.</string>
+
+    <string name="permlab_setAnimationScale">modify global animation speed</string>
+    <string name="permdesc_setAnimationScale">Allows an application to change
+        the global animation speed (faster or slower animations) at any time.</string>
+
+    <string name="permlab_manageAppTokens">manage application tokens</string>
+    <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>
+
+    <string name="permlab_injectEvents">press keys and control buttons</string>
+    <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>
+
+    <string name="permlab_readInputState">record what you type and actions you take</string>
+    <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>
+
+    <string name="permlab_setOrientation">change screen orientation</string>
+    <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>
+
+    <string name="permlab_signalPersistentProcesses">send Linux signals to applications</string>
+    <string name="permdesc_signalPersistentProcesses">Allows application to request that the
+        supplied signal be sent to all persistent processes.</string>
+
+    <string name="permlab_persistentActivity">make application always run</string>
+    <string name="permdesc_persistentActivity">Allows an application to make
+        parts of itself persistent, so the system can't use it for other
+        applications.</string>
+
+    <string name="permlab_deletePackages">delete applications</string>
+    <string name="permdesc_deletePackages">Allows an application to delete
+        Android packages. Malicious applications can use this to delete important applications.</string>
+
+    <string name="permlab_clearAppUserData">delete other application's data</string>
+    <string name="permdesc_clearAppUserData">Allows an application to clear user data.</string>
+    <string name="permlab_deleteCacheFiles">delete other application's cache</string>
+    <string name="permdesc_deleteCacheFiles">Allows an application to delete
+        cache files.</string>
+
+    <string name="permlab_getPackageSize">measure application storage space</string>
+    <string name="permdesc_getPackageSize">Allows an application to retrieve
+        its code, data, and cache sizes</string>
+
+    <string name="permlab_installPackages">directly install applications</string>
+    <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>
+
+    <string name="permlab_clearAppCache">delete all application cache data</string>
+    <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>
+
+    <string name="permlab_readLogs">read system log files</string>
+    <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>
+
+    <string name="permlab_diagnostic">read/write to resources owned by diag</string>
+    <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>
+
+    <string name="permlab_changeComponentState">enable or disable application components</string>
+    <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, inconsistant, or unstable state.
+    </string>
+
+    <string name="permlab_setPreferredApplications">set preferred applications</string>
+    <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>
+
+    <string name="permlab_writeSettings">modify global system settings</string>
+    <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_writeGservices">modify the Google services map</string>
+    <string name="permdesc_writeGservices">Allows an application to modify the
+        Google services map.  Not for use by normal applications.</string>
+
+    <string name="permlab_receiveBootCompleted">automatically start at boot</string>
+    <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>
+
+    <string name="permlab_broadcastSticky">send sticky broadcast</string>
+    <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>
+
+    <string name="permlab_readContacts">read contact data</string>
+    <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>
+
+    <string name="permlab_writeContacts">write contact data</string>
+    <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>
+
+    <string name="permlab_writeOwnerData">write owner data</string>
+    <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>
+
+    <string name="permlab_readOwnerData">read owner data</string>
+    <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>
+
+    <string name="permlab_readCalendar">read calendar data</string>
+    <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>
+
+    <string name="permlab_writeCalendar">write calendar data</string>
+    <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>
+
+    <string name="permlab_accessMockLocation">mock location sources for testing</string>
+    <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>
+
+    <string name="permlab_accessLocationExtraCommands">access extra location provider commands</string>
+    <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>
+
+    <string name="permlab_accessFineLocation">fine (GPS) location</string>
+    <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>
+
+    <string name="permlab_accessCoarseLocation">coarse (network-based) location</string>
+    <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>
+
+    <string name="permlab_accessSurfaceFlinger">access SurfaceFlinger</string>
+    <string name="permdesc_accessSurfaceFlinger">Allows application to use
+        SurfaceFlinger low-level features.</string>
+
+    <string name="permlab_readFrameBuffer">read frame buffer</string>
+    <string name="permdesc_readFrameBuffer">Allows application to use
+        read the content of the frame buffer.</string>
+
+    <string name="permlab_modifyAudioSettings">change your audio settings</string>
+    <string name="permdesc_modifyAudioSettings">Allows application to modify
+        global audio settings such as volume and routing.</string>
+
+    <string name="permlab_recordAudio">record audio</string>
+    <string name="permdesc_recordAudio">Allows application to access
+        the audio record path.</string>
+
+    <string name="permlab_camera">take pictures</string>
+    <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>
+
+    <string name="permlab_brick">permanently disable phone</string>
+    <string name="permdesc_brick">Allows the application to
+        disable the entire phone permanently. This is very dangerous.</string>
+
+    <string name="permlab_reboot">force phone reboot</string>
+    <string name="permdesc_reboot">Allows the application to
+        force the phone to reboot.</string>
+
+    <string name="permlab_mount_unmount_filesystems">mount and unmount filesystems</string>
+    <string name="permdesc_mount_unmount_filesystems">Allows the application to mount and
+        unmount filesystems for removable storage.</string>
+
+    <string name="permlab_vibrate">control vibrator</string>
+    <string name="permdesc_vibrate">Allows the application to control
+        the vibrator.</string>
+
+    <string name="permlab_flashlight">control flashlight</string>
+    <string name="permdesc_flashlight">Allows the application to control
+        the flashlight.</string>
+
+    <string name="permlab_hardware_test">test hardware</string>
+    <string name="permdesc_hardware_test">Allows the application to control
+        various peripherals for the purpose of hardware testing.</string>
+
+    <string name="permlab_callPhone">directly call phone numbers</string>
+    <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>
+
+    <string name="permlab_callPrivileged">directly call any phone numbers</string>
+    <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>
+
+    <string name="permlab_locationUpdates">control location update notifications</string>
+    <string name="permdesc_locationUpdates">Allows enabling/disabling location
+        update notifications from the radio.  Not for use by normal applications.</string>
+
+    <string name="permlab_checkinProperties">access checkin properties</string>
+    <string name="permdesc_checkinProperties">Allows read/write access to
+        properties uploaded by the checkin service.  Not for use by normal
+        applications.</string>
+
+    <string name="permlab_modifyPhoneState">modify phone state</string>
+    <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>
+
+    <string name="permlab_readPhoneState">read phone state</string>
+    <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>
+
+    <string name="permlab_wakeLock">prevent phone from sleeping</string>
+    <string name="permdesc_wakeLock">Allows an application to prevent
+        the phone from going to sleep.</string>
+
+    <string name="permlab_devicePower">power phone on or off</string>
+    <string name="permdesc_devicePower">Allows the application to turn the
+        phone on or off.</string>
+
+    <string name="permlab_factoryTest">run in factory test mode</string>
+    <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>
+
+    <string name="permlab_setWallpaper">set wallpaper</string>
+    <string name="permdesc_setWallpaper">Allows the application
+        to set the system wallpaper.</string>
+
+    <string name="permlab_setWallpaperHints">set wallpaper size hints</string>
+    <string name="permdesc_setWallpaperHints">Allows the application
+        to set the system wallpaper size hints.</string>
+
+    <string name="permlab_masterClear">reset system to factory defaults</string>
+    <string name="permdesc_masterClear">Allows an application to completely
+        reset the system to its factory settings, erasing all data,
+        configuration, and installed applications.</string>
+
+    <string name="permlab_setTimeZone">set time zone</string>
+    <string name="permdesc_setTimeZone">Allows an application to change
+        the phone's time zone.</string>
+
+    <string name="permlab_getAccounts">discover known accounts</string>
+    <string name="permdesc_getAccounts">Allows an application to get
+      the list of accounts known by the phone.</string>
+
+    <string name="permlab_accessNetworkState">view network state</string>
+    <string name="permdesc_accessNetworkState">Allows an application to view
+      the state of all networks.</string>
+
+    <string name="permlab_createNetworkSockets">full Internet access</string>
+    <string name="permdesc_createNetworkSockets">Allows an application to
+      create network sockets.</string>
+
+    <string name="permlab_writeApnSettings">write Access Point Name settings</string>
+    <string name="permdesc_writeApnSettings">Allows an application to modify the APN
+        settings, such as Proxy and Port of any APN.</string>
+
+    <string name="permlab_changeNetworkState">change network connectivity</string>
+    <string name="permdesc_changeNetworkState">Allows an application to change
+      the state network connectivity.</string>
+
+    <string name="permlab_accessWifiState">view Wi-Fi state</string>
+    <string name="permdesc_accessWifiState">Allows an application to view
+      the information about the state of Wi-Fi.</string>
+
+    <string name="permlab_changeWifiState">change Wi-Fi state</string>
+    <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>
+
+    <string name="permlab_bluetoothAdmin">bluetooth administration</string>
+    <string name="permdesc_bluetoothAdmin">Allows an application to configure
+      the local Bluetooth phone, and to discover and pair with remote
+      devices.</string>
+
+    <string name="permlab_bluetooth">create Bluetooth connections</string>
+    <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>
+
+    <string name="permlab_disableKeyguard">disable keylock</string>
+    <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>
+
+    <string name="permlab_readSyncSettings">read sync settings</string>
+    <string name="permdesc_readSyncSettings">Allows an application to read the sync settings,
+        such as whether sync is enabled for Contacts.</string>
+
+    <string name="permlab_writeSyncSettings">write sync settings</string>
+    <string name="permdesc_writeSyncSettings">Allows an application to modify the sync
+        settings, such as whether sync is enabled for Contacts.</string>
+
+    <string name="permlab_readSyncStats">read sync statistics</string>
+    <string name="permdesc_readSyncStats">Allows an application to read the sync stats; e.g., the
+        history of syncs that have occurred.</string>
+
+    <string name="permlab_subscribedFeedsRead">read subscribed feeds</string>
+    <string name="permdesc_subscribedFeedsRead">Allows an application to get details about the currently synced feeds.</string>
+    <string name="permlab_subscribedFeedsWrite">write subscribed feeds</string>
+    <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>
+
+    <!-- Phone number types from android.provider.Contacts -->
+    <string-array name="phoneTypes">
+        <!-- The order of these is important, don't reorder without changing Contacts.java -->
+        <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\u2026</item>
+    </string-array>
+
+    <!-- Email address types from android.provider.Contacts -->
+    <string-array name="emailAddressTypes">
+        <!-- The order of these is important, don't reorder without changing Contacts.java -->
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom\u2026</item>
+    </string-array>
+
+    <!-- Phone number types from android.provider.Contacts -->
+    <string-array name="postalAddressTypes">
+        <!-- The order of these is important, don't reorder without changing Contacts.java -->
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom\u2026</item>
+    </string-array>
+
+    <!-- IM types from android.provider.Contacts -->
+    <string-array name="imAddressTypes">
+        <!-- The order of these is important, don't reorder without changing Contacts.java -->
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom\u2026</item>
+    </string-array>
+
+    <!-- Organization types from android.provider.Contacts -->
+    <string-array name="organizationTypes">
+        <!-- The order of these is important, don't reorder without changing Contacts.java -->
+        <item>Home</item>
+        <item>Work</item>
+        <item>Other</item>
+        <item>Custom\u2026</item>
+    </string-array>
+
+    <!-- IM protocols from android.provider.Contacts -->
+    <string-array name="imProtocols">
+        <!-- The order of these is important, don't reorder without changing Contacts.java -->
+        <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>
+
+    <string name="keyguard_label_text">To unlock, press Menu then 0.</string>
+
+    <string name="keyguard_password_emergency_instructions">Press the Call button to make an emergency call.</string>
+    <string name="keyguard_password_instructions">Enter passcode or dial emergency number.</string>
+
+    <!-- Emergency call strings.  -->
+    <string name="emergency_call_dialog_text">Make an emergency call?</string>
+    <string name="emergency_call_dialog_cancel">Cancel</string>
+    <string name="emergency_call_dialog_call">Emergency call</string>
+    <string name="emergency_call_dialog_number_for_display">Emergency number</string>
+    <string name="emergency_call_number_uri">tel:112</string>
+
+    <!--
+       *** touch based lock / unlock ***
+                                          -->
+    <!-- the key used to look up the carrier name from system properties -->
+    <string name="lockscreen_carrier_key">gsm.operator.alpha</string>
+
+    <!-- the default display if there is no carrier (no service) -->
+    <string name="lockscreen_carrier_default">(No service)</string>
+
+    <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>
+
+    <!-- when pattern lock is disabled, only tell them to press menu to unlock -->
+    <string name="lockscreen_instructions_when_pattern_disabled">Press Menu to unlock.</string>
+
+    <string name="lockscreen_pattern_instructions">Draw pattern to unlock:</string>
+    <string name="lockscreen_emergency_call">Emergency call</string>
+    <string name="lockscreen_pattern_correct">Correct!</string>
+    <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>)</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>
+
+    <string name="lockscreen_missing_sim_message_short">No SIM card.</string>
+    <string name="lockscreen_missing_sim_message">No SIM card in phone.</string>
+    <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>
+    <string name="lockscreen_sim_puk_locked_instructions">Please contact Customer Care.</string>
+
+    <string name="lockscreen_sim_locked_message">SIM card is locked.</string>
+
+    <!-- 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 -->
+    <string name="lockscreen_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+
+    <string name="lockscreen_too_many_failed_attempts_dialog_title">Lock pattern warning</string>
+
+    <!-- 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>
+
+    <!-- Information message shown in dialog when user is almost at the limit
+         where they will be locked out and have to use their google login -->
+    <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>
+
+    <!-- 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>
+
+    <!-- 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>
+
+    <!-- glogin unlock screen -->
+    <string name="lockscreen_glogin_too_many_attempts">Too many pattern attempts!</string>
+    <string name="lockscreen_glogin_instructions">To unlock,\nsign 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="date_picker_set">Set</string>
+    <string name="date_picker_month">month</string>
+    <string name="time_picker_set">Set</string>
+
+    <string name="status_bar_date_format">"<xliff:g id="format">MMMM d, yyyy</xliff:g>"</string>
+    <string name="status_bar_time_format">"<xliff:g id="format">h:mm AA</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 label for column of application icons in the opened version of the status bar -->
+    <string name="status_bar_applications_title">Application</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">%d%%</xliff:g></string>
+
+    <!-- The big percent text in the middle of the battery icon that appears when you plug in
+         the charger. -->
+    <string name="battery_status_charging">Charging\u2026</string>
+
+    <!-- The title of the low battery alert. -->
+    <string name="battery_low_title">Please connect charger</string>
+
+    <!-- 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. -->
+    <string name="battery_low_percent_format">less than <xliff:g id="number">%d%%</xliff:g>
+    remaining.</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>
+
+    <!-- WebView User Agent -->
+    <string name="web_user_agent"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s)
+        AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2</xliff:g></string>
+
+    <!-- WebView save password dialog -->
+    <string name="save_password_label">Confirm</string>
+    <string name="save_password_message">Do you want the browser to remember this password?</string>
+    <string name="save_password_notnow">Not now</string>
+    <string name="save_password_remember">Remember</string>
+    <!-- should be "Never for this site". But it is too long, use "Never" instead -->
+    <string name="save_password_never">Never</string>
+
+    <!-- WebView permission -->
+    <string name="open_permission_deny">You do not have permission to open this page.</string>
+    
+    <!-- WebView copy text-->
+    <string name="text_copied">Text copied to clipboard.</string>
+
+    <string name="more_item_label">More</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">delete</string>
+
+    <!-- Strings used for search bar -->
+    <string name="search_go">Search</string>
+
+    <!-- Strings used to display the date -->
+    <string name="today">Today</string>
+    <string name="before">Before</string>
+    <string name="yesterday">Yesterday</string>
+    <string name="tomorrow">Tomorrow</string>
+    <string name="ago">ago</string>
+    <string name="in">in</string>
+    <string name="daysDurationFuturePlural">in <xliff:g id="days">%d</xliff:g> days</string>
+    <string name="daysDurationPastPlural"><xliff:g id="days">%d</xliff:g> days ago</string>
+    <string name="oneMonthDurationPast">1 month ago</string>
+
+    <!-- Prepositions for dates ("on May 29", "in 2008", "at 2:33am") -->
+    <string name="preposition_for_date">on %s</string>
+    <string name="preposition_for_time">at %s</string>
+    <string name="preposition_for_year">in %s</string>
+
+    <string name="day">day</string>
+    <string name="days">days</string>
+    <string name="hour">hour</string>
+    <string name="hours">hours</string>
+    <string name="minute">min</string>
+    <string name="minutes">mins</string>
+    <string name="second">sec</string>
+    <string name="seconds">secs</string>
+    <string name="week">week</string>
+    <string name="weeks">weeks</string>
+    <string name="year">year</string>
+    <string name="years">years</string>
+
+    <string name="sunday">Sunday</string>
+    <string name="monday">Monday</string>
+    <string name="tuesday">Tuesday</string>
+    <string name="wednesday">Wednesday</string>
+    <string name="thursday">Thursday</string>
+    <string name="friday">Friday</string>
+    <string name="saturday">Saturday</string>
+
+    <!-- Date formats for single line display mode -->
+    <string name="daily_format">h:mm aa</string>
+    <string name="weekly_format">MMM d</string>
+    <string name="monthly_format">MMM d</string>
+    <string name="yearly_format">yyyy</string>
+
+    <!-- TODO
+        Maybe the "mon-fri" part should be parameterized? That is, it should be something like
+        "%1$s \u2013 %2$s" and then the code that uses this resource can substitute in the local
+        3-letter abbreviation.
+    -->
+    <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
+    <string name="daily">Daily</string>
+    <string name="weekly">"Weekly on <xliff:g id="day">%s</xliff:g>"</string>
+    <string name="monthly">Monthly</string>
+    <string name="yearly">Yearly</string>
+
+
+    <!-- Errors for android.widget.VideoView -->
+    <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>
+
+
+    <!-- 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>
+
+
+    <!-- Example: "12/31/2007" -->
+    <string name="numeric_date">"<xliff:g id="format">%m/%d/%Y</xliff:g>"</string>
+
+    <!-- Example: "Mon, Dec 31, 2007, 8am - Tue, Jan 1, 2008, 5pm" -->
+    <!--   1: "Mon" -->
+    <!--   2: "Dec 31, 2007" -->
+    <!--   3: "8am" -->
+    <!--   4: "Tue" -->
+    <!--   5: "Jan 1, 2008" -->
+    <!--   6: "5pm" -->
+    <!-- or: "Friday, November 30, 8am - Thursday, December 6, 5pm" -->
+    <!--   1: "Friday" -->
+    <!--   2: "November 30" -->
+    <!--   3: "8am" -->
+    <!--   4: "Thursday" -->
+    <!--   5: "December 6" -->
+    <!--   6: "5pm" -->
+    <string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="format">%1$s, %2$s, %3$s \u2013 %4$s, %5$s, %6$s</xliff:g>"</string>
+
+    <!-- Example: "Mon, Dec 31, 2007 - Tue, Jan 1, 2008" -->
+    <!--   1: "Mon" -->
+    <!--   2: "Dec 31, 2007" -->
+    <!--   4: "Tue" -->
+    <!--   5: "Jan 1, 2008" -->
+    <!-- or: "Friday, November 30 - Thursday, December 6" -->
+    <!--   1: "Friday" -->
+    <!--   2: "November 30" -->
+    <!--   4: "Thursday" -->
+    <!--   5: "December 6" -->
+    <string name="wday1_date1_wday2_date2">"<xliff:g id="format">%1$s, %2$s \u2013 %4$s, %5$s</xliff:g>"</string>
+
+    <!-- Example: "Dec 31, 2007, 8am - Jan 1, 2008, 5pm" -->
+    <!--   2: "Dec 31, 2007" -->
+    <!--   3: "8am" -->
+    <!--   5: "Jan 1, 2008" -->
+    <!--   6: "5pm" -->
+    <!-- or: "November 30, 8am - December 6, 5pm" -->
+    <!--   2: "November 30" -->
+    <!--   3: "8am" -->
+    <!--   5: "December 6" -->
+    <!--   6: "5pm" -->
+    <string name="date1_time1_date2_time2">"<xliff:g id="format">%2$s, %3$s \u2013 %5$s, %6$s</xliff:g>"</string>
+
+    <!-- Example: "Dec 31, 2007 - Jan 1, 2008" -->
+    <!--   2: "Dec 31, 2007" -->
+    <!--   5: "Jan 1, 2008" -->
+    <!-- or: "November 30 - December 6" -->
+    <!--   2: "November 30" -->
+    <!--   5: "December 6" -->
+    <string name="date1_date2">"<xliff:g id="format">%2$s \u2013 %5$s</xliff:g>"</string>
+
+    <!-- Example: "10:00 - 11:00 am" -->
+    <!--   1: "10:00" -->
+    <!--   2: "11:00 am" -->
+    <!-- or: "10:00 am - 2:00 pm" -->
+    <!--   1: "10:00 am" -->
+    <!--   2: "2:00 pm" -->
+    <!-- or: "10:00 - 14:00" -->
+    <!--   1: "10:00" -->
+    <!--   2: "14:00" -->
+    <string name="time1_time2">"<xliff:g id="format">%1$s \u2013 %2$s</xliff:g>"</string>
+
+    <!-- Example: "8:00 - 11:00 am, Mon, Dec 31, 2007" -->
+    <!--   1: "8:00 - 11:00 am" -->
+    <!--   2: "Mon" -->
+    <!--   3: "Dec 31, 2007" -->
+    <string name="time_wday_date">"<xliff:g id="format">%1$s, %2$s, %3$s</xliff:g>"</string>
+
+    <!-- Example: "Mon, Dec 31, 2007" -->
+    <!--   2: "Mon" -->
+    <!--   3: "Dec 31, 2007" -->
+    <string name="wday_date">"<xliff:g id="format">%2$s, %3$s</xliff:g>"</string>
+
+    <!-- Example: "8:00 - 11:00 am, Dec 31, 2007" -->
+    <!--   1: "8:00 - 11:00 am" -->
+    <!--   3: "Dec 31, 2007" -->
+    <string name="time_date">"<xliff:g id="format">%1$s, %3$s</xliff:g>"</string>
+
+    <!-- Example: "8:00 - 11:00 am, Mon" -->
+    <!--   1: "8:00 - 11:00 am" -->
+    <!--   2: "Mon" -->
+    <string name="time_wday">"<xliff:g id="format">%1$s, %2$s</xliff:g>"</string>
+
+    <string name="noon">"noon"</string>
+    <string name="Noon">"Noon"</string>
+    <string name="midnight">"midnight"</string>
+    <string name="Midnight">"Midnight"</string>
+
+    <!-- Example: "October 9" -->
+    <string name="month_day">"<xliff:g id="format">%B %-d</xliff:g>"</string>
+
+    <!-- Example: "October" -->
+    <string name="month">"<xliff:g id="format">%B</xliff:g>"</string>
+
+    <!-- Example: "October 9, 2007" -->
+    <string name="month_day_year">"<xliff:g id="format">%B %-d, %Y</xliff:g>"</string>
+
+    <!-- Example: "October 2007" -->
+    <string name="month_year">"<xliff:g id="format">%B %Y</xliff:g>"</string>
+
+    <!-- Example: "Oct 31 - Nov 3" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <string name="same_year_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %7$s %8$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, Oct 31 - Sat, Nov 3" -->
+    <!--   1: "Wed" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   6: "Sat" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <string name="same_year_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 31 - Nov 3, 2007" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <string name="same_year_mdy1_mdy2">"<xliff:g id="format">%2$s %3$s \u2013 %7$s %8$s, %9$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, Oct 31 - Sat, Nov 3, 2007" -->
+    <!--   1: "Wed" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   6: "Sat" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <string name="same_year_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s, %9$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 31, 8:00am - Nov 3, 5:00pm" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   5: "8:00am" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_year_md1_time1_md2_time2">"<xliff:g id="format">%2$s %3$s, %5$s \u2013 %7$s %8$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, Oct 31, 8:00am - Sat, Nov 3, 5:00pm" -->
+    <!--   1: "Wed" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   5: "8:00am" -->
+    <!--   6: "Sat" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_year_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %5$s \u2013 %6$s, %7$s %8$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 31, 2007, 8:00am - Nov 3, 2007, 5:00pm" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   4: "2007" -->
+    <!--   5: "8:00am" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_year_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s %3$s, %4$s, %5$s \u2013 %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, Oct 31, 2007, 8:00am - Sat, Nov 3, 2007, 5:00pm" -->
+    <!--   1: "Wed" -->
+    <!--   2: "Oct" -->
+    <!--   3: "31" -->
+    <!--   4: "2007" -->
+    <!--   5: "8:00am" -->
+    <!--   6: "Sat" -->
+    <!--   7: "Nov" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s, %5$s \u2013 %6$s, %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+
+
+    <!-- Example: "10/31 - 11/3" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <string name="numeric_md1_md2">"<xliff:g id="format">%2$s/%3$s \u2013 %7$s/%8$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, 10/31 - Sat, 11/3" -->
+    <!--   1: "Wed" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   6: "Sat" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <string name="numeric_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s/%3$s \u2013 %6$s, %7$s/%8$s</xliff:g>"</string>
+
+    <!-- Example: "10/31/2007 - 11/3/2007" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   4: "2007" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <string name="numeric_mdy1_mdy2">"<xliff:g id="format">%2$s/%3$s/%4$s \u2013 %7$s/%8$s/%9$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, 10/31/2007 - Sat, 11/3/2007" -->
+    <!--   1: "Wed" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   4: "2007" -->
+    <!--   6: "Sat" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <string name="numeric_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s/%3$s/%4$s \u2013 %6$s, %7$s/%8$s/%9$s</xliff:g>"</string>
+
+    <!-- Example: "10/31, 8:00am - 11/3, 5:00pm" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   5: "8:00am" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <!--  10: "5:00pm" -->
+    <string name="numeric_md1_time1_md2_time2">"<xliff:g id="format">%2$s/%3$s, %5$s \u2013 %7$s/%8$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, 10/31, 8:00am - Sat, 11/3, 5:00pm" -->
+    <!--   1: "Wed" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   5: "8:00am" -->
+    <!--   6: "Sat" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <!--  10: "5:00pm" -->
+    <string name="numeric_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s/%3$s, %5$s \u2013 %6$s, %7$s/%8$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "10/31/2007, 8:00am - 11/3/2007, 5:00pm" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   4: "2007" -->
+    <!--   5: "8:00am" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <!--  10: "5:00pm" -->
+    <string name="numeric_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s/%3$s/%4$s, %5$s \u2013 %7$s/%8$s/%9$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Wed, 10/31/2007, 8:00am - Sat, 11/3/2007, 5:00pm" -->
+    <!--   1: "Wed" -->
+    <!--   2: "10" -->
+    <!--   3: "31" -->
+    <!--   4: "2007" -->
+    <!--   5: "8:00am" -->
+    <!--   6: "Sat" -->
+    <!--   7: "11" -->
+    <!--   8: "3" -->
+    <!--   9: "2007" -->
+    <!--  10: "5:00pm" -->
+    <string name="numeric_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s/%3$s/%4$s, %5$s \u2013 %6$s, %7$s/%8$s/%9$s, %10$s</xliff:g>"</string>
+
+
+    <!-- Example: "Oct 9 - 10" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   8: "10" -->
+    <string name="same_month_md1_md2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s</xliff:g>"</string>
+
+    <!-- Example: "Tue, Oct 9 - Wed, Oct 10" -->
+    <!--   1: "Tue" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   6: "Wed" -->
+    <!--   7: "Oct" -->
+    <!--   8: "10" -->
+    <string name="same_month_wday1_md1_wday2_md2">"<xliff:g id="format">%1$s, %2$s %3$s \u2013 %6$s, %7$s %8$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 9 - 10, 2007" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   8: "10" -->
+    <!--   9: "2007" -->
+    <string name="same_month_mdy1_mdy2">"<xliff:g id="format">%2$s %3$s \u2013 %8$s, %9$s</xliff:g>"</string>
+
+    <!-- Example: "Tue, Oct 9, 2007 - Wed, Oct 10, 2007" -->
+    <!--   1: "Tue" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   4: "2007" -->
+    <!--   6: "Wed" -->
+    <!--   7: "Oct" -->
+    <!--   8: "10" -->
+    <!--   9: "2007" -->
+    <string name="same_month_wday1_mdy1_wday2_mdy2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s \u2013 %6$s, %7$s %8$s, %9$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 9, 8:00am - Oct 10, 5:00pm" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   5: "8:00am" -->
+    <!--   7: "Oct" -->
+    <!--   8: "10" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_month_md1_time1_md2_time2">"<xliff:g id="format">%2$s %3$s, %5$s \u2013 %7$s %8$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Tue, Oct 9, 8:00am - Wed, Oct 10, 5:00pm" -->
+    <!--   1: "Tue" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   5: "8:00am" -->
+    <!--   6: "Wed" -->
+    <!--   7: "Oct" -->
+    <!--   8: "10" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_month_wday1_md1_time1_wday2_md2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %5$s \u2013 %6$s, %7$s %8$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 9, 2007, 8:00am - Oct 10, 2007, 5:00pm" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   4: "2007" -->
+    <!--   5: "8:00am" -->
+    <!--   7: "Oct" -->
+    <!--   8: "10" -->
+    <!--   9: "2007" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_month_mdy1_time1_mdy2_time2">"<xliff:g id="format">%2$s %3$s, %4$s, %5$s \u2013 %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Tue, Oct 9, 2007, 8:00am - Wed, Oct 10, 2007, 5:00pm" -->
+    <!--   1: "Tue" -->
+    <!--   2: "Oct" -->
+    <!--   3: "9" -->
+    <!--   4: "2007" -->
+    <!--   5: "8:00am" -->
+    <!--   6: "Wed" -->
+    <!--   7: "Oct" -->
+    <!--   8: "10" -->
+    <!--   9: "2007" -->
+    <!--  10: "5:00pm" -->
+    <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">"<xliff:g id="format">%1$s, %2$s %3$s, %4$s, %5$s \u2013 %6$s, %7$s %8$s, %9$s, %10$s</xliff:g>"</string>
+
+    <!-- Example: "Oct 9, 2007" -->
+    <string name="abbrev_month_day_year">"<xliff:g id="format">%b %-d, %Y</xliff:g>"</string>
+
+    <!-- Example: "Oct 2007" -->
+    <string name="abbrev_month_year">"<xliff:g id="format">%b %Y</xliff:g>"</string>
+
+    <!-- Example: "Oct 9" -->
+    <string name="abbrev_month_day">"<xliff:g id="format">%b %-d</xliff:g>"</string>
+
+    <!-- Example: "Oct" -->
+    <string name="abbrev_month">"<xliff:g id="format">%b</xliff:g>"</string>
+
+    <!-- Example: the dash in Tue, Oct 9, 2007, 8:00am - Wed, Oct 10, 2007, 5:00pm -->
+    <string name="date_range_separator">" \u2013 "</string>
+
+    <!-- Example: "10/09/2007" -->
+    <string name="numeric_date_notation">"<xliff:g id="format">%m/%d/%y</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 times like "01:23" -->
+    <string name="elapsed_time_short_format_mm_ss"><xliff:g id="format">%1$02d:%2$02d</xliff:g></string>
+
+    <!-- Format string for times like "1:43:33" -->
+    <string name="elapsed_time_short_format_h_mm_ss"><xliff:g id="format">%1$d:%2$02d:%3$02d</xliff:g></string>
+
+    <!-- EditText context menu -->
+    <string name="selectAll">Select all</string>
+
+    <!-- EditText context menu -->
+    <string name="cut">Cut</string>
+
+    <!-- EditText context menu -->
+    <string name="cutAll">Cut all</string>
+
+    <!-- EditText context menu -->
+    <string name="copy">Copy</string>
+
+    <!-- EditText context menu -->
+    <string name="copyAll">Copy all</string>
+
+    <!-- EditText context menu -->
+    <string name="paste">Paste</string>
+
+    <!-- EditText context menu -->
+    <string name="copyUrl">Copy URL</string>
+
+    <!-- Low internal storage error dialog properties title of extended view -->
+    <string name="low_internal_storage_view_title">Low on space</string>
+    <!-- Low internal storage error dialog properties text displayed in extended view -->
+    <string name="low_internal_storage_view_text">Phone storage space is getting low.</string>
+
+    <!-- Preference framework strings. -->
+    <string name="ok">OK</string>
+    <string name="cancel">Cancel</string>
+    <string name="yes">OK</string>
+    <string name="no">Cancel</string>
+
+    <!-- IndicatorButton -->
+    <string name="capital_on">ON</string>
+    <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 Intent resolution in the future. -->
+    <string name="alwaysUse">Use by default for this action.</string>
+    <!-- Text displayed when the user selects the check box for setting default application-->
+    <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. -->
+    <string name="chooseActivity">Select an action</string>
+    <!-- Text to display when there are no activities found to display in the
+         activity chooser. -->
+    <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>
+
+    <!-- Displayed in the title of the chooser for things to do with text that
+         is to be sent to another application. -->
+    <string name="sendText">Select an action for text</string>
+
+    <!-- Volume strings -->
+    <string name="volume_ringtone">Ringer volume</string>
+    <string name="volume_music">Music/video volume</string>
+    <string name="volume_call">In-call volume</string>
+    <string name="volume_alarm">Alarm volume</string>
+    <string name="volume_unknown">Volume</string>
+
+    <!-- Ringtone picker strings -->
+    <string name="ringtone_default">Default ringtone</string>
+    <string name="ringtone_default_with_actual">Default ringtone (<xliff:g id="actual_ringtone">%1$s</xliff:g>)</string>
+    <string name="ringtone_silent">Silent</string>
+    <string name="ringtone_picker_title">Select a ringtone</string>
+    <string name="ringtone_unknown">Unknown ringtone</string>
+
+    <!-- Wi-Fi strings -->
+    <plurals name="wifi_available">
+        <item quantity="one">Wi-Fi network available</item>
+        <item quantity="other">Wi-Fi networks available</item>
+    </plurals>
+    <plurals name="wifi_available_detailed">
+        <item quantity="one">Open Wi-Fi network available</item>
+        <item quantity="other">Open Wi-Fi networks available</item>
+    </plurals>
+
+    <!-- Character picker strings -->
+    <string name="select_character">Select character to insert</string>
+
+    <!-- SMS per-application rate control Dialog -->
+    <string name="sms_control_default_app_name">Unknown application</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">Cancel</string>
+
+    <!-- Date/Time Picker strings -->
+    <string name="date_time_set">Set</string>
+
+    <!-- SensorService strings -->
+    <string name="compass_accuracy_banner">Compass requires calibration</string>
+    <string name="compass_accuracy_notificaction_title">Calibrate compass</string>
+    <string name="compass_accuracy_notificaction_body">Shake phone gently to calibrate.</string>
+    <!-- Security Permissions strings-->
+    <string name="default_permission_group">Default</string>
+    <string name="permissions_format"><xliff:g id="perm_line1">%1$s</xliff:g>, <xliff:g id="perm_line2">%2$s</xliff:g></string>
+    <string name="no_permissions">No permissions required</string>
+    <string name="perms_hide"><b>Hide</b></string>
+    <string name="perms_show_all"><b>Show all</b></string>
+
+    <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. -->
+    <string name="usb_storage_activity_label">USB storage dialog</string>
+    <string name="usb_storage_title">USB connected</string>
+    <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>
+    <string name="usb_storage_button_mount">Mount</string>
+    <string name="usb_storage_button_unmount">Don\'t mount</string>
+    <string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string>
+    <string name="usb_storage_notification_title">USB connected</string>
+    <string name="usb_storage_notification_message">Select to copy files to/from your computer.</string>
+
+</resources>
+
+
+
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
new file mode 100644
index 0000000..dede6c1
--- /dev/null
+++ b/core/res/res/values/styles.xml
@@ -0,0 +1,590 @@
+<?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">
+    </style>
+
+    <!-- Standard animations for a translucent window or activity. -->
+    <style name="Animation.Translucent">
+    </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>
+
+    <!-- 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.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>
+    </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/settings_header</item>
+        <item name="android:layout_width">fill_parent</item>
+        <item name="android:layout_height">27dip</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:textColor">#FF000000</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>
+    </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.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>
+    </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>
+
+    <!-- Text Appearances -->
+    <eat-comment />
+
+    <style name="TextAppearance">
+        <item name="android:textColor">?textColorPrimary</item>
+        <item name="android:textColorHighlight">#FF1B82EB</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>
+
+    <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>
+
+    <!-- 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>
+    
+</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
new file mode 100644
index 0000000..810b5f3
--- /dev/null
+++ b/core/res/res/values/themes.xml
@@ -0,0 +1,322 @@
+<?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.5</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="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">64px</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>
+
+        <!-- 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">@android:drawable/scrollbar_horizontal</item>
+        <item name="scrollbarTrackVertical">@android:drawable/scrollbar_vertical</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="spinnerStyle">@android:style/Widget.Spinner</item>
+        <item name="starStyle">@android:style/Widget.CompoundButton.Star</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>
+        
+        <!-- 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="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="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="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="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="windowBackground">@android:color/black</item>
+        <item name="colorBackground">@android:color/black</item>
+    </style>
+    
+    <!-- Variant of the black theme with no title bar -->
+    <style name="Theme.Black.NoTitleBar">
+        <item name="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="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="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="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="windowFullscreen">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>
+    </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>
+    
+    <!-- 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>
+    </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>
+    </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/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..64d3dc8
--- /dev/null
+++ b/core/res/res/xml/autotext.xml
@@ -0,0 +1,188 @@
+<?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="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="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="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="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="tothe">to the</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="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/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..5755124
--- /dev/null
+++ b/core/res/res/xml/time_zones_by_country.xml
@@ -0,0 +1,416 @@
+<?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>
+    <timezone code="ad">Europe/Andorra</timezone>
+    <timezone code="ae">Asia/Dubai</timezone>
+    <timezone code="af">Asia/Kabul</timezone>
+    <timezone code="ag">America/Antigua</timezone>
+    <timezone code="ai">America/Anguilla</timezone>
+    <timezone code="al">Europe/Tirane</timezone>
+    <timezone code="am">Asia/Yerevan</timezone>
+    <timezone code="an">America/Curacao</timezone>
+    <timezone code="ao">Africa/Luanda</timezone>
+    <timezone code="aq">Antarctica/McMurdo</timezone>
+    <timezone code="aq">Antarctica/South_Pole</timezone>
+    <timezone code="aq">Antarctica/Rothera</timezone>
+    <timezone code="aq">Antarctica/Palmer</timezone>
+    <timezone code="aq">Antarctica/Mawson</timezone>
+    <timezone code="aq">Antarctica/Davis</timezone>
+    <timezone code="aq">Antarctica/Casey</timezone>
+    <timezone code="aq">Antarctica/Vostok</timezone>
+    <timezone code="aq">Antarctica/DumontDUrville</timezone>
+    <timezone code="aq">Antarctica/Syowa</timezone>
+    <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>
+    <timezone code="as">Pacific/Pago_Pago</timezone>
+    <timezone code="at">Europe/Vienna</timezone>
+    <timezone code="au">Australia/Lord_Howe</timezone>
+    <timezone code="au">Australia/Hobart</timezone>
+    <timezone code="au">Australia/Currie</timezone>
+    <timezone code="au">Australia/Melbourne</timezone>
+    <timezone code="au">Australia/Sydney</timezone>
+    <timezone code="au">Australia/Broken_Hill</timezone>
+    <timezone code="au">Australia/Brisbane</timezone>
+    <timezone code="au">Australia/Lindeman</timezone>
+    <timezone code="au">Australia/Adelaide</timezone>
+    <timezone code="au">Australia/Darwin</timezone>
+    <timezone code="au">Australia/Perth</timezone>
+    <timezone code="au">Australia/Eucla</timezone>
+    <timezone code="aw">America/Aruba</timezone>
+    <timezone code="ax">Europe/Mariehamn</timezone>
+    <timezone code="az">Asia/Baku</timezone>
+    <timezone code="ba">Europe/Sarajevo</timezone>
+    <timezone code="bb">America/Barbados</timezone>
+    <timezone code="bd">Asia/Dhaka</timezone>
+    <timezone code="be">Europe/Brussels</timezone>
+    <timezone code="bf">Africa/Ouagadougou</timezone>
+    <timezone code="bg">Europe/Sofia</timezone>
+    <timezone code="bh">Asia/Bahrain</timezone>
+    <timezone code="bi">Africa/Bujumbura</timezone>
+    <timezone code="bj">Africa/Porto-Novo</timezone>
+    <timezone code="bm">Atlantic/Bermuda</timezone>
+    <timezone code="bn">Asia/Brunei</timezone>
+    <timezone code="bo">America/La_Paz</timezone>
+    <timezone code="br">America/Noronha</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>
+    <timezone code="br">America/Sao_Paulo</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/Manaus</timezone>
+    <timezone code="br">America/Eirunepe</timezone>
+    <timezone code="br">America/Rio_Branco</timezone>
+    <timezone code="bs">America/Nassau</timezone>
+    <timezone code="bt">Asia/Thimphu</timezone>
+    <timezone code="bw">Africa/Gaborone</timezone>
+    <timezone code="by">Europe/Minsk</timezone>
+    <timezone code="bz">America/Belize</timezone>
+    <timezone code="ca">America/St_Johns</timezone>
+    <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>
+    <timezone code="ca">America/Montreal</timezone>
+    <timezone code="ca">America/Toronto</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>
+    <timezone code="ca">America/Rankin_Inlet</timezone>
+    <timezone code="ca">America/Winnipeg</timezone>
+    <timezone code="ca">America/Rainy_River</timezone>
+    <timezone code="ca">America/Cambridge_Bay</timezone>
+    <timezone code="ca">America/Regina</timezone>
+    <timezone code="ca">America/Swift_Current</timezone>
+    <timezone code="ca">America/Edmonton</timezone>
+    <timezone code="ca">America/Yellowknife</timezone>
+    <timezone code="ca">America/Inuvik</timezone>
+    <timezone code="ca">America/Dawson_Creek</timezone>
+    <timezone code="ca">America/Vancouver</timezone>
+    <timezone code="ca">America/Whitehorse</timezone>
+    <timezone code="ca">America/Dawson</timezone>
+    <timezone code="cc">Indian/Cocos</timezone>
+    <timezone code="cd">Africa/Kinshasa</timezone>
+    <timezone code="cd">Africa/Lubumbashi</timezone>
+    <timezone code="cf">Africa/Bangui</timezone>
+    <timezone code="cg">Africa/Brazzaville</timezone>
+    <timezone code="ch">Europe/Zurich</timezone>
+    <timezone code="ci">Africa/Abidjan</timezone>
+    <timezone code="ck">Pacific/Rarotonga</timezone>
+    <timezone code="cl">America/Santiago</timezone>
+    <timezone code="cl">Pacific/Easter</timezone>
+    <timezone code="cm">Africa/Douala</timezone>
+    <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>
+    <timezone code="co">America/Bogota</timezone>
+    <timezone code="cr">America/Costa_Rica</timezone>
+    <timezone code="cu">America/Havana</timezone>
+    <timezone code="cv">Atlantic/Cape_Verde</timezone>
+    <timezone code="cx">Indian/Christmas</timezone>
+    <timezone code="cy">Asia/Nicosia</timezone>
+    <timezone code="cz">Europe/Prague</timezone>
+    <timezone code="de">Europe/Berlin</timezone>
+    <timezone code="dj">Africa/Djibouti</timezone>
+    <timezone code="dk">Europe/Copenhagen</timezone>
+    <timezone code="dm">America/Dominica</timezone>
+    <timezone code="do">America/Santo_Domingo</timezone>
+    <timezone code="dz">Africa/Algiers</timezone>
+    <timezone code="ec">America/Guayaquil</timezone>
+    <timezone code="ec">Pacific/Galapagos</timezone>
+    <timezone code="ee">Europe/Tallinn</timezone>
+    <timezone code="eg">Africa/Cairo</timezone>
+    <timezone code="eh">Africa/El_Aaiun</timezone>
+    <timezone code="er">Africa/Asmara</timezone>
+    <timezone code="es">Europe/Madrid</timezone>
+    <timezone code="es">Africa/Ceuta</timezone>
+    <timezone code="es">Atlantic/Canary</timezone>
+    <timezone code="et">Africa/Addis_Ababa</timezone>
+    <timezone code="fi">Europe/Helsinki</timezone>
+    <timezone code="fj">Pacific/Fiji</timezone>
+    <timezone code="fk">Atlantic/Stanley</timezone>
+    <timezone code="fm">Pacific/Truk</timezone>
+    <timezone code="fm">Pacific/Ponape</timezone>
+    <timezone code="fm">Pacific/Kosrae</timezone>
+    <timezone code="fo">Atlantic/Faroe</timezone>
+    <timezone code="fr">Europe/Paris</timezone>
+    <timezone code="ga">Africa/Libreville</timezone>
+    <timezone code="gb">Europe/London</timezone>
+    <timezone code="gd">America/Grenada</timezone>
+    <timezone code="ge">Asia/Tbilisi</timezone>
+    <timezone code="gf">America/Cayenne</timezone>
+    <timezone code="gg">Europe/Guernsey</timezone>
+    <timezone code="gh">Africa/Accra</timezone>
+    <timezone code="gi">Europe/Gibraltar</timezone>
+    <timezone code="gl">America/Godthab</timezone>
+    <timezone code="gl">America/Danmarkshavn</timezone>
+    <timezone code="gl">America/Scoresbysund</timezone>
+    <timezone code="gl">America/Thule</timezone>
+    <timezone code="gm">Africa/Banjul</timezone>
+    <timezone code="gn">Africa/Conakry</timezone>
+    <timezone code="gp">America/Guadeloupe</timezone>
+    <timezone code="gq">Africa/Malabo</timezone>
+    <timezone code="gr">Europe/Athens</timezone>
+    <timezone code="gs">Atlantic/South_Georgia</timezone>
+    <timezone code="gt">America/Guatemala</timezone>
+    <timezone code="gu">Pacific/Guam</timezone>
+    <timezone code="gw">Africa/Bissau</timezone>
+    <timezone code="gy">America/Guyana</timezone>
+    <timezone code="hk">Asia/Hong_Kong</timezone>
+    <timezone code="hn">America/Tegucigalpa</timezone>
+    <timezone code="hr">Europe/Zagreb</timezone>
+    <timezone code="ht">America/Port-au-Prince</timezone>
+    <timezone code="hu">Europe/Budapest</timezone>
+    <timezone code="id">Asia/Jakarta</timezone>
+    <timezone code="id">Asia/Pontianak</timezone>
+    <timezone code="id">Asia/Makassar</timezone>
+    <timezone code="id">Asia/Jayapura</timezone>
+    <timezone code="ie">Europe/Dublin</timezone>
+    <timezone code="il">Asia/Jerusalem</timezone>
+    <timezone code="im">Europe/Isle_of_Man</timezone>
+    <timezone code="in">Asia/Calcutta</timezone>
+    <timezone code="io">Indian/Chagos</timezone>
+    <timezone code="iq">Asia/Baghdad</timezone>
+    <timezone code="ir">Asia/Tehran</timezone>
+    <timezone code="is">Atlantic/Reykjavik</timezone>
+    <timezone code="it">Europe/Rome</timezone>
+    <timezone code="je">Europe/Jersey</timezone>
+    <timezone code="jm">America/Jamaica</timezone>
+    <timezone code="jo">Asia/Amman</timezone>
+    <timezone code="jp">Asia/Tokyo</timezone>
+    <timezone code="ke">Africa/Nairobi</timezone>
+    <timezone code="kg">Asia/Bishkek</timezone>
+    <timezone code="kh">Asia/Phnom_Penh</timezone>
+    <timezone code="ki">Pacific/Tarawa</timezone>
+    <timezone code="ki">Pacific/Enderbury</timezone>
+    <timezone code="ki">Pacific/Kiritimati</timezone>
+    <timezone code="km">Indian/Comoro</timezone>
+    <timezone code="kn">America/St_Kitts</timezone>
+    <timezone code="kp">Asia/Pyongyang</timezone>
+    <timezone code="kr">Asia/Seoul</timezone>
+    <timezone code="kw">Asia/Kuwait</timezone>
+    <timezone code="ky">America/Cayman</timezone>
+    <timezone code="kz">Asia/Almaty</timezone>
+    <timezone code="kz">Asia/Qyzylorda</timezone>
+    <timezone code="kz">Asia/Aqtobe</timezone>
+    <timezone code="kz">Asia/Aqtau</timezone>
+    <timezone code="kz">Asia/Oral</timezone>
+    <timezone code="la">Asia/Vientiane</timezone>
+    <timezone code="lb">Asia/Beirut</timezone>
+    <timezone code="lc">America/St_Lucia</timezone>
+    <timezone code="li">Europe/Vaduz</timezone>
+    <timezone code="lk">Asia/Colombo</timezone>
+    <timezone code="lr">Africa/Monrovia</timezone>
+    <timezone code="ls">Africa/Maseru</timezone>
+    <timezone code="lt">Europe/Vilnius</timezone>
+    <timezone code="lu">Europe/Luxembourg</timezone>
+    <timezone code="lv">Europe/Riga</timezone>
+    <timezone code="ly">Africa/Tripoli</timezone>
+    <timezone code="ma">Africa/Casablanca</timezone>
+    <timezone code="mc">Europe/Monaco</timezone>
+    <timezone code="md">Europe/Chisinau</timezone>
+    <timezone code="me">Europe/Podgorica</timezone>
+    <timezone code="mg">Indian/Antananarivo</timezone>
+    <timezone code="mh">Pacific/Majuro</timezone>
+    <timezone code="mh">Pacific/Kwajalein</timezone>
+    <timezone code="mk">Europe/Skopje</timezone>
+    <timezone code="ml">Africa/Bamako</timezone>
+    <timezone code="mm">Asia/Rangoon</timezone>
+    <timezone code="mn">Asia/Ulaanbaatar</timezone>
+    <timezone code="mn">Asia/Hovd</timezone>
+    <timezone code="mn">Asia/Choibalsan</timezone>
+    <timezone code="mo">Asia/Macau</timezone>
+    <timezone code="mp">Pacific/Saipan</timezone>
+    <timezone code="mq">America/Martinique</timezone>
+    <timezone code="mr">Africa/Nouakchott</timezone>
+    <timezone code="ms">America/Montserrat</timezone>
+    <timezone code="mt">Europe/Malta</timezone>
+    <timezone code="mu">Indian/Mauritius</timezone>
+    <timezone code="mv">Indian/Maldives</timezone>
+    <timezone code="mw">Africa/Blantyre</timezone>
+    <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>
+    <timezone code="mx">America/Mazatlan</timezone>
+    <timezone code="mx">America/Chihuahua</timezone>
+    <timezone code="mx">America/Hermosillo</timezone>
+    <timezone code="mx">America/Tijuana</timezone>
+    <timezone code="my">Asia/Kuala_Lumpur</timezone>
+    <timezone code="my">Asia/Kuching</timezone>
+    <timezone code="mz">Africa/Maputo</timezone>
+    <timezone code="na">Africa/Windhoek</timezone>
+    <timezone code="nc">Pacific/Noumea</timezone>
+    <timezone code="ne">Africa/Niamey</timezone>
+    <timezone code="nf">Pacific/Norfolk</timezone>
+    <timezone code="ng">Africa/Lagos</timezone>
+    <timezone code="ni">America/Managua</timezone>
+    <timezone code="nl">Europe/Amsterdam</timezone>
+    <timezone code="no">Europe/Oslo</timezone>
+    <timezone code="np">Asia/Katmandu</timezone>
+    <timezone code="nr">Pacific/Nauru</timezone>
+    <timezone code="nu">Pacific/Niue</timezone>
+    <timezone code="nz">Pacific/Auckland</timezone>
+    <timezone code="nz">Pacific/Chatham</timezone>
+    <timezone code="om">Asia/Muscat</timezone>
+    <timezone code="pa">America/Panama</timezone>
+    <timezone code="pe">America/Lima</timezone>
+    <timezone code="pf">Pacific/Tahiti</timezone>
+    <timezone code="pf">Pacific/Marquesas</timezone>
+    <timezone code="pf">Pacific/Gambier</timezone>
+    <timezone code="pg">Pacific/Port_Moresby</timezone>
+    <timezone code="ph">Asia/Manila</timezone>
+    <timezone code="pk">Asia/Karachi</timezone>
+    <timezone code="pl">Europe/Warsaw</timezone>
+    <timezone code="pm">America/Miquelon</timezone>
+    <timezone code="pn">Pacific/Pitcairn</timezone>
+    <timezone code="pr">America/Puerto_Rico</timezone>
+    <timezone code="ps">Asia/Gaza</timezone>
+    <timezone code="pt">Europe/Lisbon</timezone>
+    <timezone code="pt">Atlantic/Madeira</timezone>
+    <timezone code="pt">Atlantic/Azores</timezone>
+    <timezone code="pw">Pacific/Palau</timezone>
+    <timezone code="py">America/Asuncion</timezone>
+    <timezone code="qa">Asia/Qatar</timezone>
+    <timezone code="re">Indian/Reunion</timezone>
+    <timezone code="ro">Europe/Bucharest</timezone>
+    <timezone code="rs">Europe/Belgrade</timezone>
+    <timezone code="ru">Europe/Kaliningrad</timezone>
+    <timezone code="ru">Europe/Moscow</timezone>
+    <timezone code="ru">Europe/Volgograd</timezone>
+    <timezone code="ru">Europe/Samara</timezone>
+    <timezone code="ru">Asia/Yekaterinburg</timezone>
+    <timezone code="ru">Asia/Omsk</timezone>
+    <timezone code="ru">Asia/Novosibirsk</timezone>
+    <timezone code="ru">Asia/Krasnoyarsk</timezone>
+    <timezone code="ru">Asia/Irkutsk</timezone>
+    <timezone code="ru">Asia/Yakutsk</timezone>
+    <timezone code="ru">Asia/Vladivostok</timezone>
+    <timezone code="ru">Asia/Sakhalin</timezone>
+    <timezone code="ru">Asia/Magadan</timezone>
+    <timezone code="ru">Asia/Kamchatka</timezone>
+    <timezone code="ru">Asia/Anadyr</timezone>
+    <timezone code="rw">Africa/Kigali</timezone>
+    <timezone code="sa">Asia/Riyadh</timezone>
+    <timezone code="sb">Pacific/Guadalcanal</timezone>
+    <timezone code="sc">Indian/Mahe</timezone>
+    <timezone code="sd">Africa/Khartoum</timezone>
+    <timezone code="se">Europe/Stockholm</timezone>
+    <timezone code="sg">Asia/Singapore</timezone>
+    <timezone code="sh">Atlantic/St_Helena</timezone>
+    <timezone code="si">Europe/Ljubljana</timezone>
+    <timezone code="sj">Arctic/Longyearbyen</timezone>
+    <timezone code="sj">Atlantic/Jan_Mayen</timezone>
+    <timezone code="sk">Europe/Bratislava</timezone>
+    <timezone code="sl">Africa/Freetown</timezone>
+    <timezone code="sm">Europe/San_Marino</timezone>
+    <timezone code="sn">Africa/Dakar</timezone>
+    <timezone code="so">Africa/Mogadishu</timezone>
+    <timezone code="sr">America/Paramaribo</timezone>
+    <timezone code="st">Africa/Sao_Tome</timezone>
+    <timezone code="sv">America/El_Salvador</timezone>
+    <timezone code="sy">Asia/Damascus</timezone>
+    <timezone code="sz">Africa/Mbabane</timezone>
+    <timezone code="tc">America/Grand_Turk</timezone>
+    <timezone code="td">Africa/Ndjamena</timezone>
+    <timezone code="tf">Indian/Kerguelen</timezone>
+    <timezone code="tg">Africa/Lome</timezone>
+    <timezone code="th">Asia/Bangkok</timezone>
+    <timezone code="tj">Asia/Dushanbe</timezone>
+    <timezone code="tk">Pacific/Fakaofo</timezone>
+    <timezone code="tl">Asia/Dili</timezone>
+    <timezone code="tm">Asia/Ashgabat</timezone>
+    <timezone code="tn">Africa/Tunis</timezone>
+    <timezone code="to">Pacific/Tongatapu</timezone>
+    <timezone code="tr">Europe/Istanbul</timezone>
+    <timezone code="tt">America/Port_of_Spain</timezone>
+    <timezone code="tv">Pacific/Funafuti</timezone>
+    <timezone code="tw">Asia/Taipei</timezone>
+    <timezone code="tz">Africa/Dar_es_Salaam</timezone>
+    <timezone code="ua">Europe/Kiev</timezone>
+    <timezone code="ua">Europe/Uzhgorod</timezone>
+    <timezone code="ua">Europe/Zaporozhye</timezone>
+    <timezone code="ua">Europe/Simferopol</timezone>
+    <timezone code="ug">Africa/Kampala</timezone>
+    <timezone code="um">Pacific/Johnston</timezone>
+    <timezone code="um">Pacific/Midway</timezone>
+    <timezone code="um">Pacific/Wake</timezone>
+    <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/Knox</timezone>
+    <timezone code="us">America/Indiana/Winamac</timezone>
+    <timezone code="us">America/Indiana/Marengo</timezone>
+    <timezone code="us">America/Indiana/Vevay</timezone>
+    <timezone code="us">America/Chicago</timezone>
+    <timezone code="us">America/Indiana/Vincennes</timezone>
+    <timezone code="us">America/Indiana/Petersburg</timezone>
+    <timezone code="us">America/Menominee</timezone>
+    <timezone code="us">America/North_Dakota/Center</timezone>
+    <timezone code="us">America/North_Dakota/New_Salem</timezone>
+    <timezone code="us">America/Denver</timezone>
+    <timezone code="us">America/Boise</timezone>
+    <timezone code="us">America/Shiprock</timezone>
+    <timezone code="us">America/Phoenix</timezone>
+    <timezone code="us">America/Los_Angeles</timezone>
+    <timezone code="us">America/Anchorage</timezone>
+    <timezone code="us">America/Juneau</timezone>
+    <timezone code="us">America/Yakutat</timezone>
+    <timezone code="us">America/Nome</timezone>
+    <timezone code="us">America/Adak</timezone>
+    <timezone code="us">Pacific/Honolulu</timezone>
+    <timezone code="uy">America/Montevideo</timezone>
+    <timezone code="uz">Asia/Samarkand</timezone>
+    <timezone code="uz">Asia/Tashkent</timezone>
+    <timezone code="va">Europe/Vatican</timezone>
+    <timezone code="vc">America/St_Vincent</timezone>
+    <timezone code="ve">America/Caracas</timezone>
+    <timezone code="vg">America/Tortola</timezone>
+    <timezone code="vi">America/St_Thomas</timezone>
+    <timezone code="vn">Asia/Saigon</timezone>
+    <timezone code="vu">Pacific/Efate</timezone>
+    <timezone code="wf">Pacific/Wallis</timezone>
+    <timezone code="ws">Pacific/Apia</timezone>
+    <timezone code="ye">Asia/Aden</timezone>
+    <timezone code="yt">Indian/Mayotte</timezone>
+    <timezone code="za">Africa/Johannesburg</timezone>
+    <timezone code="zm">Africa/Lusaka</timezone>
+    <timezone code="zw">Africa/Harare</timezone>
+</timezones>
diff --git a/core/res/waitingroom/screen_background_blue-land.png b/core/res/waitingroom/screen_background_blue-land.png
new file mode 100644
index 0000000..50e46b1
--- /dev/null
+++ b/core/res/waitingroom/screen_background_blue-land.png
Binary files differ
diff --git a/core/res/waitingroom/screen_background_green-land.png b/core/res/waitingroom/screen_background_green-land.png
new file mode 100644
index 0000000..f46afa1
--- /dev/null
+++ b/core/res/waitingroom/screen_background_green-land.png
Binary files differ
diff --git a/core/res/waitingroom/screen_background_white-land.png b/core/res/waitingroom/screen_background_white-land.png
new file mode 100644
index 0000000..a179c38
--- /dev/null
+++ b/core/res/waitingroom/screen_background_white-land.png
Binary files differ