am fab096cb: Merge change 22103 into donut

Merge commit 'fab096cbcf98cb98eef25e7541121ae8e3898a10' into eclair

* commit 'fab096cbcf98cb98eef25e7541121ae8e3898a10':
  new transparent icon for searchable dictionary
diff --git a/apps/Development/AndroidManifest.xml b/apps/Development/AndroidManifest.xml
index 4f8df3e..0bdd26e 100644
--- a/apps/Development/AndroidManifest.xml
+++ b/apps/Development/AndroidManifest.xml
@@ -31,6 +31,9 @@
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.YouTubeUser" />
     <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
 
     <application android:label="Dev Tools"
             android:icon="@drawable/ic_launcher_devtools">
@@ -73,6 +76,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="AccountsTester" android:label="AccountsTester"
+                  android:theme="@android:style/Theme.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="DataList">
         </activity>
         <activity android:name="Details">
diff --git a/apps/Development/res/layout/account_list_context_menu.xml b/apps/Development/res/layout/account_list_context_menu.xml
new file mode 100644
index 0000000..50e7ca1
--- /dev/null
+++ b/apps/Development/res/layout/account_list_context_menu.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/accounts_tester_remove_account"
+        android:title="@string/accounts_tester_remove_account" />
+
+    <item android:id="@+id/accounts_tester_get_auth_token"
+        android:title="@string/accounts_tester_get_auth_token" />
+
+    <item android:id="@+id/accounts_tester_invalidate_auth_token"
+        android:title="@string/accounts_tester_invalidate_auth_token" />
+
+    <item android:id="@+id/accounts_tester_update_credentials"
+        android:title="@string/accounts_tester_update_credentials" />
+
+    <item android:id="@+id/accounts_tester_confirm_credentials"
+        android:title="@string/accounts_tester_confirm_credentials" />
+
+</menu>
diff --git a/apps/Development/res/layout/accounts_tester.xml b/apps/Development/res/layout/accounts_tester.xml
new file mode 100644
index 0000000..e69f505
--- /dev/null
+++ b/apps/Development/res/layout/accounts_tester.xml
@@ -0,0 +1,117 @@
+<?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:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <LinearLayout
+       android:orientation="vertical"
+       android:layout_width="fill_parent"
+       android:layout_height="wrap_content">
+
+        <ListView android:id="@+id/accounts_tester_authenticators_list"
+           android:layout_width="fill_parent" android:layout_height="fill_parent"/>
+
+      <LinearLayout
+         android:orientation="horizontal"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content">
+
+          <TextView android:id="@+id/accounts_tester_account_types_spinner_label"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/accounts_tester_select_account_type"/>
+
+          <Spinner android:id="@+id/accounts_tester_account_types_spinner"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"/>
+      </LinearLayout>
+
+      <LinearLayout
+         android:orientation="vertical"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content">
+          <LinearLayout
+             android:orientation="horizontal"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content">
+         <Button
+            android:id="@+id/accounts_tester_get_accounts_by_type"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/accounts_tester_get_accounts_by_type"/>
+
+         <Button
+            android:id="@+id/accounts_tester_get_all_accounts"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/accounts_tester_get_all_accounts"/>
+              <Button android:id="@+id/accounts_tester_add_account"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:text="@string/accounts_tester_add_account"/>
+
+              <Button android:id="@+id/accounts_tester_edit_properties"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:text="@string/accounts_tester_edit_properties"/>
+          </LinearLayout>
+
+          <LinearLayout
+             android:orientation="horizontal"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content">
+              <TextView android:id="@+id/accounts_tester_desiredFeatures"
+                       android:layout_width="wrap_content"
+                       android:layout_height="wrap_content"
+                       android:text="@string/accounts_tester_desired_features_label"/>
+
+          <EditText android:id="@+id/accounts_tester_desired_features"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:minEms="15"/>
+          </LinearLayout>
+          <LinearLayout
+             android:orientation="horizontal"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content">
+              <TextView android:id="@+id/accounts_tester_desiredFeatures"
+                       android:layout_width="wrap_content"
+                       android:layout_height="wrap_content"
+                       android:text="@string/accounts_tester_desired_authtokentype_label"/>
+
+          <EditText android:id="@+id/accounts_tester_desired_authtokentype"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:minEms="15"/>
+          </LinearLayout>
+       </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout
+       android:orientation="horizontal"
+       android:layout_width="fill_parent"
+       android:layout_height="wrap_content">
+
+       <ListView android:id="@+id/accounts_tester_accounts_list"
+           android:layout_width="fill_parent" android:layout_height="fill_parent"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/apps/Development/res/layout/authenticators_list_item.xml b/apps/Development/res/layout/authenticators_list_item.xml
new file mode 100644
index 0000000..309a818
--- /dev/null
+++ b/apps/Development/res/layout/authenticators_list_item.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * 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.
+ */
+-->
+
+<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:paddingTop="1dip"
+    android:paddingBottom="1dip"
+    android:paddingLeft="9dip"
+    android:paddingRight="9dip">
+
+    <ImageView android:id="@+id/accounts_tester_authenticator_icon"
+        android:paddingRight="9dip"
+        android:layout_gravity="center_vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView android:id="@+id/accounts_tester_authenticator_label"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:layout_gravity="center_horizontal|center_vertical" />
+</LinearLayout>
+
diff --git a/apps/Development/res/layout/get_auth_token_view.xml b/apps/Development/res/layout/get_auth_token_view.xml
new file mode 100644
index 0000000..f371523
--- /dev/null
+++ b/apps/Development/res/layout/get_auth_token_view.xml
@@ -0,0 +1,36 @@
+<?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:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <TextView android:id="@+id/accounts_tester_get_auth_token_dialog_message"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/accounts_tester_enter_auth_token_type" />
+
+    <EditText android:id="@+id/accounts_tester_auth_token_type"
+              android:singleLine="true"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:minWidth="250dip"
+              android:scrollHorizontally="true"
+              android:capitalize="none"
+              android:autoText="false"/>
+
+</LinearLayout>
diff --git a/apps/Development/res/values/strings.xml b/apps/Development/res/values/strings.xml
index 4c84548..21f15f6 100644
--- a/apps/Development/res/values/strings.xml
+++ b/apps/Development/res/values/strings.xml
@@ -133,4 +133,23 @@
     <string name="perm_list_header_text">Apps using permission  </string>
     <string name="source_uid_text">Source uid :  </string>
     <string name="shared_pkgs_text">Packages accessing via shared uid :  </string>
+
+    <!-- AccountsTester -->
+    <string name="accounts_tester_app_name">Accounts Tester</string>
+    <string name="accounts_tester_get_accounts_by_type">Get By Type</string>
+    <string name="accounts_tester_get_all_accounts">Get All</string>
+    <string name="accounts_tester_add_account">Add</string>
+    <string name="accounts_tester_select_account_type">Select Account Type</string>
+    <string name="accounts_tester_process_name_header">Process Name:</string>
+    <string name="accounts_tester_remove_account">remove</string>
+    <string name="accounts_tester_get_auth_token">authenticate</string>
+    <string name="accounts_tester_invalidate_auth_token">invalidate token</string>
+    <string name="accounts_tester_account_context_menu_title">account operations</string>
+    <string name="accounts_tester_do_get_auth_token">Ok</string>
+    <string name="accounts_tester_enter_auth_token_type">Enter the authtoken type:</string>
+    <string name="accounts_tester_update_credentials">Update Credentials</string>
+    <string name="accounts_tester_confirm_credentials">Confirm Credentials</string>
+    <string name="accounts_tester_edit_properties">Properties</string>
+    <string name="accounts_tester_desired_authtokentype_label">authtoken type:</string>
+    <string name="accounts_tester_desired_features_label">features:</string>
 </resources>
diff --git a/apps/Development/src/com/android/development/AccountsTester.java b/apps/Development/src/com/android/development/AccountsTester.java
new file mode 100644
index 0000000..2525f4a
--- /dev/null
+++ b/apps/Development/src/com/android/development/AccountsTester.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.development;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.content.*;
+import android.content.pm.PackageManager;
+import android.accounts.*;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Handler;
+import android.view.*;
+import android.widget.*;
+import android.widget.ArrayAdapter;
+import android.util.Log;
+import android.text.TextUtils;
+
+import java.io.IOException;
+
+public class AccountsTester extends Activity implements OnAccountsUpdatedListener {
+    private static final String TAG = "AccountsTester";
+    private Spinner mAccountTypesSpinner;
+    private ListView mAccountsListView;
+    private ListView mAuthenticatorsListView;
+    private AccountManager mAccountManager;
+    private String mLongPressedAccount = null;
+    private static final String COM_GOOGLE_GAIA = "com.google.GAIA";
+    private AuthenticatorDescription[] mAuthenticatorDescs;
+
+    private static final int GET_AUTH_TOKEN_DIALOG_ID = 1;
+    private static final int UPDATE_CREDENTIALS_DIALOG_ID = 2;
+    private static final int INVALIDATE_AUTH_TOKEN_DIALOG_ID = 3;
+    private EditText mDesiredAuthTokenTypeEditText;
+    private EditText mDesiredFeaturesEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAccountManager = AccountManager.get(this);
+        setContentView(R.layout.accounts_tester);
+        ButtonClickListener buttonClickListener = new ButtonClickListener();
+
+        mAccountTypesSpinner = (Spinner) findViewById(R.id.accounts_tester_account_types_spinner);
+        mAccountsListView = (ListView) findViewById(R.id.accounts_tester_accounts_list);
+        mAuthenticatorsListView = (ListView) findViewById(R.id.accounts_tester_authenticators_list);
+        registerForContextMenu(mAccountsListView);
+        getAuthenticatorTypes();
+        findViewById(R.id.accounts_tester_get_all_accounts).setOnClickListener(buttonClickListener);
+        findViewById(R.id.accounts_tester_get_accounts_by_type).setOnClickListener(
+                buttonClickListener);
+        findViewById(R.id.accounts_tester_add_account).setOnClickListener(buttonClickListener);
+        findViewById(R.id.accounts_tester_edit_properties).setOnClickListener(buttonClickListener);
+        mDesiredAuthTokenTypeEditText =
+                (EditText) findViewById(R.id.accounts_tester_desired_authtokentype);
+        mDesiredFeaturesEditText = (EditText) findViewById(R.id.accounts_tester_desired_features);
+    }
+
+    private static class AuthenticatorsArrayAdapter extends ArrayAdapter<AuthenticatorDescription> {
+        protected LayoutInflater mInflater;
+        private static final int mResource = R.layout.authenticators_list_item;
+
+        public AuthenticatorsArrayAdapter(Context context, AuthenticatorDescription[] items) {
+            super(context, mResource, items);
+            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        static class ViewHolder {
+            TextView label;
+            ImageView icon;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // A ViewHolder keeps references to children views to avoid unneccessary calls
+            // to findViewById() on each row.
+            ViewHolder holder;
+
+            // When convertView is not null, we can reuse it directly, there is no need
+            // to reinflate it. We only inflate a new View when the convertView supplied
+            // by ListView is null.
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.authenticators_list_item, null);
+
+                // Creates a ViewHolder and store references to the two children views
+                // we want to bind data to.
+                holder = new ViewHolder();
+                holder.label = (TextView) convertView.findViewById(
+                        R.id.accounts_tester_authenticator_label);
+                holder.icon = (ImageView) convertView.findViewById(
+                        R.id.accounts_tester_authenticator_icon);
+
+                convertView.setTag(holder);
+            } else {
+                // Get the ViewHolder back to get fast access to the TextView
+                // and the ImageView.
+                holder = (ViewHolder) convertView.getTag();
+            }
+
+            final AuthenticatorDescription desc = getItem(position);
+            final String packageName = desc.packageName;
+            try {
+                final Context authContext = getContext().createPackageContext(packageName, 0);
+
+                // Set text field
+                holder.label.setText(authContext.getString(desc.labelId));
+
+                // Set resource icon
+                holder.icon.setImageDrawable(authContext.getResources().getDrawable(desc.iconId));
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.d(TAG, "error getting the Package Context for " + packageName, e);
+            }
+
+            return convertView;
+        }
+    }
+
+    private void getAuthenticatorTypes() {
+        mAuthenticatorDescs = mAccountManager.getAuthenticatorTypes();
+        String[] names = new String[mAuthenticatorDescs.length];
+        for (int i = 0; i < mAuthenticatorDescs.length; i++) {
+            Context authContext;
+            try {
+                authContext = createPackageContext(mAuthenticatorDescs[i].packageName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                continue;
+            }
+            names[i] = authContext.getString(mAuthenticatorDescs[i].labelId);
+        }
+
+        ArrayAdapter<String> adapter =
+                new ArrayAdapter<String>(AccountsTester.this,
+                android.R.layout.simple_spinner_item, names);
+        mAccountTypesSpinner.setAdapter(adapter);
+
+        mAuthenticatorsListView.setAdapter(new AuthenticatorsArrayAdapter(
+                AccountsTester.this, mAuthenticatorDescs));
+    }
+
+    public void onAccountsUpdated(Account[] accounts) {
+        Log.d(TAG, "onAccountsUpdated: \n  " + TextUtils.join("\n  ", accounts));
+        String[] accountNames = new String[accounts.length];
+        for (int i = 0; i < accounts.length; i++) {
+            accountNames[i] = accounts[i].name;
+        }
+        ArrayAdapter<String> adapter =
+                new ArrayAdapter<String>(AccountsTester.this,
+                android.R.layout.simple_list_item_1, accountNames);
+        mAccountsListView.setAdapter(adapter);
+    }
+
+    protected void onStart() {
+        super.onStart();
+        final Handler mainHandler = new Handler(getMainLooper());
+        mAccountManager.addOnAccountsUpdatedListener(this, mainHandler,
+                true /* updateImmediately */);
+    }
+
+    protected void onStop() {
+        super.onStop();
+        mAccountManager.removeOnAccountsUpdatedListener(this);
+    }
+
+    class ButtonClickListener implements View.OnClickListener {
+        public void onClick(View v) {
+            if (R.id.accounts_tester_get_all_accounts == v.getId()) {
+                onAccountsUpdated(mAccountManager.getAccounts());
+            } else if (R.id.accounts_tester_get_accounts_by_type == v.getId()) {
+                String type = getSelectedAuthenticator().type;
+                onAccountsUpdated(mAccountManager.getAccountsByType(type));
+            } else if (R.id.accounts_tester_add_account == v.getId()) {
+                AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+                    public void run(AccountManagerFuture<Bundle> future) {
+                        try {
+                            Bundle bundle = future.getResult();
+                            bundle.keySet();
+                            Log.d(TAG, "account added: " + bundle);
+                        } catch (OperationCanceledException e) {
+                            Log.d(TAG, "addAccount was canceled");
+                        } catch (IOException e) {
+                            Log.d(TAG, "addAccount failed: " + e);
+                        } catch (AuthenticatorException e) {
+                            Log.d(TAG, "addAccount failed: " + e);
+                        }
+                    }
+                };
+                String authTokenType = mDesiredAuthTokenTypeEditText.getText().toString();
+                if (TextUtils.isEmpty(authTokenType)) {
+                    authTokenType = null;
+                }
+                String featureString = mDesiredFeaturesEditText.getText().toString();
+                String[] requiredFeatures = TextUtils.split(featureString, " ");
+                if (requiredFeatures.length == 0) {
+                    requiredFeatures = null;
+                }
+                mAccountManager.addAccount(getSelectedAuthenticator().type,
+                        authTokenType, requiredFeatures, null /* options */,
+                        AccountsTester.this, callback, null /* handler */);
+            } else if (R.id.accounts_tester_edit_properties == v.getId()) {
+                mAccountManager.editProperties(getSelectedAuthenticator().type,
+                        AccountsTester.this, new EditPropertiesCallback(), null /* handler */);
+            } else {
+                // unknown button
+            }
+        }
+
+        private class EditPropertiesCallback implements AccountManagerCallback<Bundle> {
+            public void run(AccountManagerFuture<Bundle> future) {
+                try {
+                    Bundle bundle = future.getResult();
+                    bundle.keySet();
+                    Log.d(TAG, "editProperties succeeded: " + bundle);
+                } catch (OperationCanceledException e) {
+                    Log.d(TAG, "editProperties was canceled");
+                } catch (IOException e) {
+                    Log.d(TAG, "editProperties failed: ", e);
+                } catch (AuthenticatorException e) {
+                    Log.d(TAG, "editProperties failed: ", e);
+                }
+            }
+        }
+    }
+
+    private AuthenticatorDescription getSelectedAuthenticator() {
+        return mAuthenticatorDescs[mAccountTypesSpinner.getSelectedItemPosition()];
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v,
+            ContextMenu.ContextMenuInfo menuInfo) {
+        menu.setHeaderTitle(R.string.accounts_tester_account_context_menu_title);
+
+        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.layout.account_list_context_menu, menu);
+        mLongPressedAccount = ((TextView)info.targetView).getText().toString();
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.accounts_tester_remove_account) {
+            final Account account = new Account(mLongPressedAccount, COM_GOOGLE_GAIA);
+            mAccountManager.removeAccount(account, new AccountManagerCallback<Boolean>() {
+                public void run(AccountManagerFuture<Boolean> future) {
+                    try {
+                        Log.d(TAG, "removeAccount(" + account + ") = " + future.getResult());
+                    } catch (OperationCanceledException e) {
+                    } catch (IOException e) {
+                    } catch (AuthenticatorException e) {
+                    }
+                }
+            }, null /* handler */);
+        } else if (item.getItemId() == R.id.accounts_tester_get_auth_token) {
+            showDialog(GET_AUTH_TOKEN_DIALOG_ID);
+        } else if (item.getItemId() == R.id.accounts_tester_invalidate_auth_token) {
+            showDialog(INVALIDATE_AUTH_TOKEN_DIALOG_ID);
+        } else if (item.getItemId() == R.id.accounts_tester_update_credentials) {
+            showDialog(UPDATE_CREDENTIALS_DIALOG_ID);
+        } else if (item.getItemId() == R.id.accounts_tester_confirm_credentials) {
+            mAccountManager.confirmCredentials(new Account(mLongPressedAccount, COM_GOOGLE_GAIA),
+                    AccountsTester.this, new ConfirmCredentialsCallback(), null /* handler */);
+        }
+        return true;
+    }
+
+    @Override
+    protected Dialog onCreateDialog(final int id) {
+        if (id == GET_AUTH_TOKEN_DIALOG_ID || id == INVALIDATE_AUTH_TOKEN_DIALOG_ID
+                || id == UPDATE_CREDENTIALS_DIALOG_ID) {
+            final View view = LayoutInflater.from(this).inflate(R.layout.get_auth_token_view, null);
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setPositiveButton(R.string.accounts_tester_do_get_auth_token,
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            EditText value = (EditText) view.findViewById(
+                                    R.id.accounts_tester_auth_token_type);
+
+                            String authTokenType = value.getText().toString();
+                            AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
+                                public void run(AccountManagerFuture<Bundle> future) {
+                                    try {
+                                        Bundle bundle = future.getResult();
+                                        bundle.keySet();
+                                        Log.d(TAG, "dialog " + id + " success: " + bundle);
+                                    } catch (OperationCanceledException e) {
+                                        Log.d(TAG, "dialog " + id + " canceled");
+                                    } catch (IOException e) {
+                                        Log.d(TAG, "dialog " + id + " failed: " + e);
+                                    } catch (AuthenticatorException e) {
+                                        Log.d(TAG, "dialog " + id + " failed: " + e);
+                                    }
+                                }
+                            };
+                            final Account account = new Account(mLongPressedAccount,
+                                    COM_GOOGLE_GAIA);
+                            if (id == GET_AUTH_TOKEN_DIALOG_ID) {
+                                mAccountManager.getAuthToken(account, authTokenType,
+                                        null /* loginOptions */, AccountsTester.this,
+                                        callback, null /* handler */);
+                            } else if (id == INVALIDATE_AUTH_TOKEN_DIALOG_ID) {
+                                mAccountManager.getAuthToken(account, authTokenType, false,
+                                        new GetAndInvalidateAuthTokenCallback(), null);
+                            } else {
+                                mAccountManager.updateCredentials(
+                                        account,
+                                        authTokenType, null /* loginOptions */,
+                                        AccountsTester.this, callback, null /* handler */);
+                            }
+                        }
+            });
+            builder.setView(view);
+            return builder.create();
+        }
+        return super.onCreateDialog(id);
+    }
+
+    AccountManagerCallback<Bundle> newAccountsCallback(String type, String[] features) {
+        return new GetAccountsCallback(type, features);
+    }
+
+    class GetAccountsCallback implements AccountManagerCallback<Bundle> {
+        final String[] mFeatures;
+        final String mAccountType;
+
+        public GetAccountsCallback(String type, String[] features) {
+            mFeatures = features;
+            mAccountType = type;
+        }
+
+        public void run(AccountManagerFuture<Bundle> future) {
+            Log.d(TAG, "GetAccountsCallback: type " + mAccountType
+                    + ", features "
+                    + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures)));
+            try {
+                Bundle result = future.getResult();
+                Parcelable[] accounts = result.getParcelableArray(Constants.ACCOUNTS_KEY);
+                Log.d(TAG, "found " + accounts.length + " accounts");
+                for (Parcelable account : accounts) {
+                    Log.d(TAG, "  " + account);
+                }
+            } catch (OperationCanceledException e) {
+                Log.d(TAG, "failure", e);
+            } catch (IOException e) {
+                Log.d(TAG, "failure", e);
+            } catch (AuthenticatorException e) {
+                Log.d(TAG, "failure", e);
+            }
+        }
+    }
+
+    AccountManagerCallback<Bundle> newAuthTokensCallback(String type, String authTokenType, String[] features) {
+        return new GetAuthTokenCallback(type, authTokenType, features);
+    }
+
+    class GetAuthTokenCallback implements AccountManagerCallback<Bundle> {
+        final String[] mFeatures;
+        final String mAccountType;
+        final String mAuthTokenType;
+
+        public GetAuthTokenCallback(String type, String authTokenType, String[] features) {
+            mFeatures = features;
+            mAccountType = type;
+            mAuthTokenType = authTokenType;
+        }
+
+        public void run(AccountManagerFuture<Bundle> future) {
+            Log.d(TAG, "GetAuthTokenCallback: type " + mAccountType
+                    + ", features "
+                    + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures)));
+            try {
+                Bundle result = future.getResult();
+                result.keySet();
+                Log.d(TAG, "  result: " + result);
+            } catch (OperationCanceledException e) {
+                Log.d(TAG, "failure", e);
+            } catch (IOException e) {
+                Log.d(TAG, "failure", e);
+            } catch (AuthenticatorException e) {
+                Log.d(TAG, "failure", e);
+            }
+        }
+    }
+
+    private class GetAndInvalidateAuthTokenCallback implements AccountManagerCallback<Bundle> {
+        public void run(AccountManagerFuture<Bundle> future) {
+            try {
+                Bundle bundle = future.getResult();
+                String authToken = bundle.getString(Constants.AUTHTOKEN_KEY);
+                mAccountManager.invalidateAuthToken(COM_GOOGLE_GAIA, authToken);
+            } catch (OperationCanceledException e) {
+                Log.d(TAG, "invalidate: interrupted while getting authToken");
+            } catch (IOException e) {
+                Log.d(TAG, "invalidate: error getting authToken", e);
+            } catch (AuthenticatorException e) {
+                Log.d(TAG, "invalidate: error getting authToken", e);
+            }
+        }
+    }
+
+    private static class ConfirmCredentialsCallback implements AccountManagerCallback<Bundle> {
+        public void run(AccountManagerFuture<Bundle> future) {
+            try {
+                Bundle bundle = future.getResult();
+                bundle.keySet();
+                Log.d(TAG, "confirmCredentials success: " + bundle);
+            } catch (OperationCanceledException e) {
+                Log.d(TAG, "confirmCredentials canceled");
+            } catch (AuthenticatorException e) {
+                Log.d(TAG, "confirmCredentials failed: " + e);
+            } catch (IOException e) {
+                Log.d(TAG, "confirmCredentials failed: " + e);
+            }
+        }
+    }
+}
diff --git a/apps/Development/src/com/android/development/PointerLocation.java b/apps/Development/src/com/android/development/PointerLocation.java
index 668e9ba..3969c9d 100644
--- a/apps/Development/src/com/android/development/PointerLocation.java
+++ b/apps/Development/src/com/android/development/PointerLocation.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.Paint.FontMetricsInt;
 import android.os.Bundle;
 import android.util.Log;
@@ -47,16 +46,9 @@
         getWindow().setAttributes(lp);
     }
     
-    public class MyView extends View {
-        private final Paint mTextPaint;
-        private final Paint mTextBackgroundPaint;
-        private final Paint mTextLevelPaint;
-        private final Paint mPaint;
-        private final Paint mTargetPaint;
-        private final FontMetricsInt mTextMetrics = new FontMetricsInt();
+    public static class PointerState {
         private final ArrayList<Float> mXs = new ArrayList<Float>();
         private final ArrayList<Float> mYs = new ArrayList<Float>();
-        private int mHeaderBottom;
         private boolean mCurDown;
         private int mCurX;
         private int mCurY;
@@ -64,12 +56,29 @@
         private float mCurSize;
         private int mCurWidth;
         private VelocityTracker mVelocity;
+    }
+    
+    public class MyView extends View {
+        private final Paint mTextPaint;
+        private final Paint mTextBackgroundPaint;
+        private final Paint mTextLevelPaint;
+        private final Paint mPaint;
+        private final Paint mTargetPaint;
+        private final Paint mPathPaint;
+        private final FontMetricsInt mTextMetrics = new FontMetricsInt();
+        private int mHeaderBottom;
+        private boolean mCurDown;
+        private int mCurNumPointers;
+        private int mMaxNumPointers;
+        private final ArrayList<PointerState> mPointers
+                 = new ArrayList<PointerState>();
         
         public MyView(Context c) {
             super(c);
             mTextPaint = new Paint();
             mTextPaint.setAntiAlias(true);
-            mTextPaint.setTextSize(10);
+            mTextPaint.setTextSize(10
+                    * getResources().getDisplayMetrics().density);
             mTextPaint.setARGB(255, 0, 0, 0);
             mTextBackgroundPaint = new Paint();
             mTextBackgroundPaint.setAntiAlias(false);
@@ -85,8 +94,15 @@
             mTargetPaint = new Paint();
             mTargetPaint.setAntiAlias(false);
             mTargetPaint.setARGB(192, 0, 0, 255);
+            mPathPaint = new Paint();
+            mPathPaint.setAntiAlias(false);
+            mPathPaint.setARGB(255, 64, 128, 255);
             mPaint.setStyle(Paint.Style.STROKE);
             mPaint.setStrokeWidth(1);
+            
+            PointerState ps = new PointerState();
+            ps.mVelocity = VelocityTracker.obtain();
+            mPointers.add(ps);
         }
 
         @Override
@@ -103,58 +119,102 @@
 
         @Override
         protected void onDraw(Canvas canvas) {
-            int w = getWidth()/5;
-            int base = -mTextMetrics.ascent+1;
-            int bottom = mHeaderBottom;
-            canvas.drawRect(0, 0, w-1, bottom, mTextBackgroundPaint);
-            canvas.drawText("X: " + mCurX, 1, base, mTextPaint);
-            canvas.drawRect(w, 0, (w * 2) - 1, bottom, mTextBackgroundPaint);
-            canvas.drawText("Y: " + mCurY, 1 + w, base, mTextPaint);
-            canvas.drawRect(w * 2, 0, (w * 3) - 1, bottom, mTextBackgroundPaint);
-            canvas.drawRect(w * 2, 0, (w * 2) + (mCurPressure * w) - 1, bottom, mTextLevelPaint);
-            canvas.drawText("Pres: " + mCurPressure, 1 + w * 2, base, mTextPaint);
-            canvas.drawRect(w * 3, 0, (w * 4) - 1, bottom, mTextBackgroundPaint);
-            canvas.drawRect(w * 3, 0, (w * 3) + (mCurSize * w) - 1, bottom, mTextLevelPaint);
-            canvas.drawText("Size: " + mCurSize, 1 + w * 3, base, mTextPaint);
-            canvas.drawRect(w * 4, 0, getWidth(), bottom, mTextBackgroundPaint);
-            int velocity = mVelocity == null ? 0 : (int) (mVelocity.getYVelocity() * 1000);
-            canvas.drawText("yVel: " + velocity, 1 + w * 4, base, mTextPaint);
+            final int w = getWidth();
+            final int itemW = w/7;
+            final int base = -mTextMetrics.ascent+1;
+            final int bottom = mHeaderBottom;
             
-            final int N = mXs.size();
-            float lastX=0, lastY=0;
-            mPaint.setARGB(255, 0, 255, 255);
-            for (int i=0; i<N; i++) {
-                float x = mXs.get(i);
-                float y = mYs.get(i);
-                if (i > 0) {
-                    canvas.drawLine(lastX, lastY, x, y, mTargetPaint);
-                    canvas.drawPoint(lastX, lastY, mPaint);
+            final int NP = mPointers.size();
+            
+            if (NP > 0) {
+                final PointerState ps = mPointers.get(0);
+                canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
+                canvas.drawText("P: " + mCurNumPointers + " / " + mMaxNumPointers,
+                        1, base, mTextPaint);
+                
+                canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
+                canvas.drawText("X: " + ps.mCurX, 1 + itemW, base, mTextPaint);
+                
+                canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
+                canvas.drawText("Y: " + ps.mCurY, 1 + itemW * 2, base, mTextPaint);
+                
+                canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
+                int velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getXVelocity() * 1000);
+                canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint);
+                
+                canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
+                velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getYVelocity() * 1000);
+                canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint);
+                
+                canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
+                canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCurPressure * itemW) - 1,
+                        bottom, mTextLevelPaint);
+                canvas.drawText("Prs: " + String.format("%.2f", ps.mCurPressure), 1 + itemW * 5,
+                        base, mTextPaint);
+                
+                canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
+                canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCurSize * itemW) - 1,
+                        bottom, mTextLevelPaint);
+                canvas.drawText("Size: " + String.format("%.2f", ps.mCurSize), 1 + itemW * 6,
+                        base, mTextPaint);
+            }
+            
+            for (int p=0; p<NP; p++) {
+                final PointerState ps = mPointers.get(p);
+                
+                final int N = ps.mXs.size();
+                float lastX=0, lastY=0;
+                boolean haveLast = false;
+                boolean drawn = false;
+                mPaint.setARGB(255, 128, 255, 255);
+                for (int i=0; i<N; i++) {
+                    float x = ps.mXs.get(i);
+                    float y = ps.mYs.get(i);
+                    if (Float.isNaN(x)) {
+                        haveLast = false;
+                        continue;
+                    }
+                    if (haveLast) {
+                        canvas.drawLine(lastX, lastY, x, y, mPathPaint);
+                        canvas.drawPoint(lastX, lastY, mPaint);
+                        drawn = true;
+                    }
+                    lastX = x;
+                    lastY = y;
+                    haveLast = true;
                 }
-                lastX = x;
-                lastY = y;
-            }
-            if (mVelocity != null) {
-                mPaint.setARGB(255, 255, 0, 0);
-                float xVel = mVelocity.getXVelocity() * (1000/60);
-                float yVel = mVelocity.getYVelocity() * (1000/60);
-                canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
-            } else {
-                canvas.drawPoint(lastX, lastY, mPaint);
-            }
-            
-            if (mCurDown) {
-                canvas.drawLine(0, (int)mCurY, getWidth(), (int)mCurY, mTargetPaint);
-                canvas.drawLine((int)mCurX, 0, (int)mCurX, getHeight(), mTargetPaint);
-                int pressureLevel = (int)(mCurPressure*255);
-                mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel);
-                canvas.drawPoint(mCurX, mCurY, mPaint);
-                canvas.drawCircle(mCurX, mCurY, mCurWidth, mPaint);
+                
+                if (drawn) {
+                    if (ps.mVelocity != null) {
+                        mPaint.setARGB(255, 255, 64, 128);
+                        float xVel = ps.mVelocity.getXVelocity() * (1000/60);
+                        float yVel = ps.mVelocity.getYVelocity() * (1000/60);
+                        canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
+                    } else {
+                        canvas.drawPoint(lastX, lastY, mPaint);
+                    }
+                }
+                
+                if (mCurDown && ps.mCurDown) {
+                    canvas.drawLine(0, (int)ps.mCurY, getWidth(), (int)ps.mCurY, mTargetPaint);
+                    canvas.drawLine((int)ps.mCurX, 0, (int)ps.mCurX, getHeight(), mTargetPaint);
+                    int pressureLevel = (int)(ps.mCurPressure*255);
+                    mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel);
+                    canvas.drawPoint(ps.mCurX, ps.mCurY, mPaint);
+                    canvas.drawCircle(ps.mCurX, ps.mCurY, ps.mCurWidth, mPaint);
+                }
             }
         }
 
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             int action = event.getAction();
+            
+            //Log.i("Pointer", "Motion: action=0x" + Integer.toHexString(action)
+            //        + " pointers=" + event.getPointerCount());
+            
+            int NP = mPointers.size();
+            
             //mRect.set(0, 0, getWidth(), mHeaderBottom+1);
             //invalidate(mRect);
             //if (mCurDown) {
@@ -164,26 +224,68 @@
             //    mRect.setEmpty();
             //}
             if (action == MotionEvent.ACTION_DOWN) {
-                mXs.clear();
-                mYs.clear();
-                mVelocity = VelocityTracker.obtain();
+                for (int p=0; p<NP; p++) {
+                    final PointerState ps = mPointers.get(p);
+                    ps.mXs.clear();
+                    ps.mYs.clear();
+                    ps.mVelocity = VelocityTracker.obtain();
+                    ps.mCurDown = false;
+                }
+                mPointers.get(0).mCurDown = true;
             }
-            mVelocity.addMovement(event);
-            mVelocity.computeCurrentVelocity(1);
-            final int N = event.getHistorySize();
-            for (int i=0; i<N; i++) {
-                mXs.add(event.getHistoricalX(i));
-                mYs.add(event.getHistoricalY(i));
+            
+            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
+                final int id = (action&MotionEvent.ACTION_POINTER_ID_MASK)
+                        >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+                while (NP <= id) {
+                    PointerState ps = new PointerState();
+                    ps.mVelocity = VelocityTracker.obtain();
+                    mPointers.add(ps);
+                    NP++;
+                }
+                final PointerState ps = mPointers.get(id);
+                ps.mVelocity = VelocityTracker.obtain();
+                ps.mCurDown = true;
             }
-            mXs.add(event.getX());
-            mYs.add(event.getY());
-            mCurDown = action == MotionEvent.ACTION_DOWN
-                    || action == MotionEvent.ACTION_MOVE;
-            mCurX = (int)event.getX();
-            mCurY = (int)event.getY();
-            mCurPressure = event.getPressure();
-            mCurSize = event.getSize();
-            mCurWidth = (int)(mCurSize*(getWidth()/3));
+            
+            final int NI = event.getPointerCount();
+            
+            mCurDown = action != MotionEvent.ACTION_UP
+                    && action != MotionEvent.ACTION_CANCEL;
+            mCurNumPointers = mCurDown ? NI : 0;
+            if (mMaxNumPointers < mCurNumPointers) {
+                mMaxNumPointers = mCurNumPointers;
+            }
+            
+            for (int i=0; i<NI; i++) {
+                final PointerState ps = mPointers.get(event.getPointerId(i));
+                ps.mVelocity.addMovement(event);
+                ps.mVelocity.computeCurrentVelocity(1);
+                final int N = event.getHistorySize();
+                for (int j=0; j<N; j++) {
+                    ps.mXs.add(event.getHistoricalX(i, j));
+                    ps.mYs.add(event.getHistoricalY(i, j));
+                }
+                ps.mXs.add(event.getX(i));
+                ps.mYs.add(event.getY(i));
+                ps.mCurX = (int)event.getX(i);
+                ps.mCurY = (int)event.getY(i);
+                //Log.i("Pointer", "Pointer #" + p + ": (" + ps.mCurX
+                //        + "," + ps.mCurY + ")");
+                ps.mCurPressure = event.getPressure(i);
+                ps.mCurSize = event.getSize(i);
+                ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3));
+            }
+            
+            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
+                final int id = (action&MotionEvent.ACTION_POINTER_ID_MASK)
+                        >> MotionEvent.ACTION_POINTER_ID_SHIFT;
+                final PointerState ps = mPointers.get(id);
+                ps.mXs.add(Float.NaN);
+                ps.mYs.add(Float.NaN);
+                ps.mCurDown = false;
+            }
+            
             //if (mCurDown) {
             //    mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
             //            mCurX+mCurWidth+3, mCurY+mCurWidth+3);
diff --git a/apps/Fallback/res/values-cs/strings.xml b/apps/Fallback/res/values-cs/strings.xml
index 1fc121c..b9d34f9 100644
--- a/apps/Fallback/res/values-cs/strings.xml
+++ b/apps/Fallback/res/values-cs/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Záloha"</string>
-    <string name="title" msgid="8156274565006125136">"Akce není podporována"</string>
-    <string name="error" msgid="6539615832923362301">"Tato akce není momentálně podporována."</string>
+    <string name="appTitle">"Záloha"</string>
+    <string name="title">"Akce není podporována"</string>
+    <string name="error">"Tato akce není momentálně podporována."</string>
 </resources>
diff --git a/apps/Fallback/res/values-da/strings.xml b/apps/Fallback/res/values-da/strings.xml
index 4584e61..b3dfa63 100644
--- a/apps/Fallback/res/values-da/strings.xml
+++ b/apps/Fallback/res/values-da/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Reserve"</string>
-    <string name="title" msgid="8156274565006125136">"Ikke understøttet handling"</string>
-    <string name="error" msgid="6539615832923362301">"Handlingen er ikke understøttet i øjeblikket."</string>
+    <string name="appTitle">"Reserve"</string>
+    <string name="title">"Ikke understøttet handling"</string>
+    <string name="error">"Handlingen er ikke understøttet i øjeblikket."</string>
 </resources>
diff --git a/apps/Fallback/res/values-de/strings.xml b/apps/Fallback/res/values-de/strings.xml
index 90bbb16..8d59ddf 100644
--- a/apps/Fallback/res/values-de/strings.xml
+++ b/apps/Fallback/res/values-de/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Nicht unterstützte Aktion"</string>
-    <string name="error" msgid="6539615832923362301">"Diese Aktion wird zurzeit nicht unterstützt."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Nicht unterstützte Aktion"</string>
+    <string name="error">"Diese Aktion wird zurzeit nicht unterstützt."</string>
 </resources>
diff --git a/apps/Fallback/res/values-el/strings.xml b/apps/Fallback/res/values-el/strings.xml
index b985af7..fecaf4a 100644
--- a/apps/Fallback/res/values-el/strings.xml
+++ b/apps/Fallback/res/values-el/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Εναλλακτική"</string>
-    <string name="title" msgid="8156274565006125136">"Ενέργεια που δεν υποστηρίζεται"</string>
-    <string name="error" msgid="6539615832923362301">"Αυτή η ενέργεια δεν υποστηρίζεται αυτήν τη στιγμή."</string>
+    <string name="appTitle">"Εναλλακτική"</string>
+    <string name="title">"Ενέργεια που δεν υποστηρίζεται"</string>
+    <string name="error">"Αυτή η ενέργεια δεν υποστηρίζεται αυτήν τη στιγμή."</string>
 </resources>
diff --git a/apps/Fallback/res/values-es-rUS/strings.xml b/apps/Fallback/res/values-es-rUS/strings.xml
index d15a287..0ce5751 100644
--- a/apps/Fallback/res/values-es-rUS/strings.xml
+++ b/apps/Fallback/res/values-es-rUS/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Acción no admitida"</string>
-    <string name="error" msgid="6539615832923362301">"Esa acción no se admite actualmente."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Acción no admitida"</string>
+    <string name="error">"Esa acción no se admite actualmente."</string>
 </resources>
diff --git a/apps/Fallback/res/values-es/strings.xml b/apps/Fallback/res/values-es/strings.xml
index d15a287..0ce5751 100644
--- a/apps/Fallback/res/values-es/strings.xml
+++ b/apps/Fallback/res/values-es/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Acción no admitida"</string>
-    <string name="error" msgid="6539615832923362301">"Esa acción no se admite actualmente."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Acción no admitida"</string>
+    <string name="error">"Esa acción no se admite actualmente."</string>
 </resources>
diff --git a/apps/Fallback/res/values-fr/strings.xml b/apps/Fallback/res/values-fr/strings.xml
index df1e299..024ae42 100644
--- a/apps/Fallback/res/values-fr/strings.xml
+++ b/apps/Fallback/res/values-fr/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Application de secours"</string>
-    <string name="title" msgid="8156274565006125136">"Action non prise en charge"</string>
-    <string name="error" msgid="6539615832923362301">"Cette action n\'est actuellement pas prise en charge."</string>
+    <string name="appTitle">"Application de secours"</string>
+    <string name="title">"Action non prise en charge"</string>
+    <string name="error">"Cette action n\'est actuellement pas prise en charge."</string>
 </resources>
diff --git a/apps/Fallback/res/values-it/strings.xml b/apps/Fallback/res/values-it/strings.xml
index 2d08c1b..d216e59 100644
--- a/apps/Fallback/res/values-it/strings.xml
+++ b/apps/Fallback/res/values-it/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Azione non supportata"</string>
-    <string name="error" msgid="6539615832923362301">"L\'azione non è al momento supportata."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Azione non supportata"</string>
+    <string name="error">"L\'azione non è al momento supportata."</string>
 </resources>
diff --git a/apps/Fallback/res/values-ja/strings.xml b/apps/Fallback/res/values-ja/strings.xml
index f220909..79aeb42 100644
--- a/apps/Fallback/res/values-ja/strings.xml
+++ b/apps/Fallback/res/values-ja/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"サポートされていない操作"</string>
-    <string name="error" msgid="6539615832923362301">"現在サポートされていない操作です。"</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"サポートされていない操作"</string>
+    <string name="error">"現在サポートされていない操作です。"</string>
 </resources>
diff --git a/apps/Fallback/res/values-ko/strings.xml b/apps/Fallback/res/values-ko/strings.xml
index 2c2973f..ec1c330 100644
--- a/apps/Fallback/res/values-ko/strings.xml
+++ b/apps/Fallback/res/values-ko/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"폴백"</string>
-    <string name="title" msgid="8156274565006125136">"지원되지 않는 작업"</string>
-    <string name="error" msgid="6539615832923362301">"이 작업은 현재 지원되지 않습니다."</string>
+    <string name="appTitle">"폴백"</string>
+    <string name="title">"지원되지 않는 작업"</string>
+    <string name="error">"이 작업은 현재 지원되지 않습니다."</string>
 </resources>
diff --git a/apps/Fallback/res/values-nb/strings.xml b/apps/Fallback/res/values-nb/strings.xml
index 02814e6..6fed660 100644
--- a/apps/Fallback/res/values-nb/strings.xml
+++ b/apps/Fallback/res/values-nb/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Ustøttet handling"</string>
-    <string name="error" msgid="6539615832923362301">"Denne handlingen er ikke støttet nå."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Ustøttet handling"</string>
+    <string name="error">"Denne handlingen er ikke støttet nå."</string>
 </resources>
diff --git a/apps/Fallback/res/values-nl/strings.xml b/apps/Fallback/res/values-nl/strings.xml
index 8989efd..f347964 100644
--- a/apps/Fallback/res/values-nl/strings.xml
+++ b/apps/Fallback/res/values-nl/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Reserve"</string>
-    <string name="title" msgid="8156274565006125136">"Niet-ondersteunde actie"</string>
-    <string name="error" msgid="6539615832923362301">"Die actie wordt momenteel niet ondersteund."</string>
+    <string name="appTitle">"Reserve"</string>
+    <string name="title">"Niet-ondersteunde actie"</string>
+    <string name="error">"Die actie wordt momenteel niet ondersteund."</string>
 </resources>
diff --git a/apps/Fallback/res/values-pl/strings.xml b/apps/Fallback/res/values-pl/strings.xml
index 5740498..73a176a 100644
--- a/apps/Fallback/res/values-pl/strings.xml
+++ b/apps/Fallback/res/values-pl/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Wycofanie"</string>
-    <string name="title" msgid="8156274565006125136">"Nieobsługiwana czynność"</string>
-    <string name="error" msgid="6539615832923362301">"Ta czynność nie jest aktualnie obsługiwana."</string>
+    <string name="appTitle">"Wycofanie"</string>
+    <string name="title">"Nieobsługiwana czynność"</string>
+    <string name="error">"Ta czynność nie jest aktualnie obsługiwana."</string>
 </resources>
diff --git a/apps/Fallback/res/values-pt-rPT/strings.xml b/apps/Fallback/res/values-pt-rPT/strings.xml
index b226ea5..3c7ec9d 100644
--- a/apps/Fallback/res/values-pt-rPT/strings.xml
+++ b/apps/Fallback/res/values-pt-rPT/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Acção não suportada"</string>
-    <string name="error" msgid="6539615832923362301">"Esta acção ainda não é suportada."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Acção não suportada"</string>
+    <string name="error">"Esta·acção·ainda·não·é·suportada."</string>
 </resources>
diff --git a/apps/Fallback/res/values-pt/strings.xml b/apps/Fallback/res/values-pt/strings.xml
index 395004c..08a3faa 100644
--- a/apps/Fallback/res/values-pt/strings.xml
+++ b/apps/Fallback/res/values-pt/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Ação não suportada"</string>
-    <string name="error" msgid="6539615832923362301">"Essa ação não é suportada no momento."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Ação não suportada"</string>
+    <string name="error">"Essa ação não é suportada no momento."</string>
 </resources>
diff --git a/apps/Fallback/res/values-ru/strings.xml b/apps/Fallback/res/values-ru/strings.xml
index 084c3f3..24c3480 100644
--- a/apps/Fallback/res/values-ru/strings.xml
+++ b/apps/Fallback/res/values-ru/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Переход в обходной режим"</string>
-    <string name="title" msgid="8156274565006125136">"Неподдерживаемое действие"</string>
-    <string name="error" msgid="6539615832923362301">"В настоящее время это действие не поддерживается."</string>
+    <string name="appTitle">"Переход в обходной режим"</string>
+    <string name="title">"Неподдерживаемое действие"</string>
+    <string name="error">"В настоящее время это действие не поддерживается."</string>
 </resources>
diff --git a/apps/Fallback/res/values-sv/strings.xml b/apps/Fallback/res/values-sv/strings.xml
index 224d946..9dae10d 100644
--- a/apps/Fallback/res/values-sv/strings.xml
+++ b/apps/Fallback/res/values-sv/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Reserv"</string>
-    <string name="title" msgid="8156274565006125136">"Åtgärden stöds inte"</string>
-    <string name="error" msgid="6539615832923362301">"Den här åtgärden stöds inte för närvarande."</string>
+    <string name="appTitle">"Reserv"</string>
+    <string name="title">"Åtgärden stöds inte"</string>
+    <string name="error">"Den här åtgärden stöds inte för närvarande."</string>
 </resources>
diff --git a/apps/Fallback/res/values-tr/strings.xml b/apps/Fallback/res/values-tr/strings.xml
index 9c7ed15..27b860a 100644
--- a/apps/Fallback/res/values-tr/strings.xml
+++ b/apps/Fallback/res/values-tr/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"Fallback"</string>
-    <string name="title" msgid="8156274565006125136">"Desteklenmeyen işlem"</string>
-    <string name="error" msgid="6539615832923362301">"Bu işlem şu an desteklenmiyor."</string>
+    <string name="appTitle">"Fallback"</string>
+    <string name="title">"Desteklenmeyen işlem"</string>
+    <string name="error">"Bu işlem şu an desteklenmiyor."</string>
 </resources>
diff --git a/apps/Fallback/res/values-zh-rCN/strings.xml b/apps/Fallback/res/values-zh-rCN/strings.xml
index 5b1fbf8..e6cfde1 100644
--- a/apps/Fallback/res/values-zh-rCN/strings.xml
+++ b/apps/Fallback/res/values-zh-rCN/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"后备"</string>
-    <string name="title" msgid="8156274565006125136">"不支持此操作"</string>
-    <string name="error" msgid="6539615832923362301">"目前不支持该操作。"</string>
+    <string name="appTitle">"后备"</string>
+    <string name="title">"不支持此操作"</string>
+    <string name="error">"目前不支持该操作。"</string>
 </resources>
diff --git a/apps/Fallback/res/values-zh-rTW/strings.xml b/apps/Fallback/res/values-zh-rTW/strings.xml
index 04d7f4f..52afdbe 100644
--- a/apps/Fallback/res/values-zh-rTW/strings.xml
+++ b/apps/Fallback/res/values-zh-rTW/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="appTitle" msgid="161410001913116606">"備用"</string>
-    <string name="title" msgid="8156274565006125136">"不支援的操作"</string>
-    <string name="error" msgid="6539615832923362301">"目前不支援此操作。"</string>
+    <string name="appTitle">"備用"</string>
+    <string name="title">"不支援的操作"</string>
+    <string name="error">"目前不支援此操作。"</string>
 </resources>
diff --git a/apps/Term/Android.mk b/apps/Term/Android.mk
index 843aec5..9ff6c0d 100644
--- a/apps/Term/Android.mk
+++ b/apps/Term/Android.mk
@@ -1,3 +1,26 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 makefile shows how to build a shared library and an activity that
+# bundles the shared library and calls it using JNI.
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -7,4 +30,11 @@
 
 LOCAL_PACKAGE_NAME := Term
 
+LOCAL_JNI_SHARED_LIBRARIES := libterm
+
 include $(BUILD_PACKAGE)
+
+# ============================================================
+
+# Also build all of the sub-targets under this one: the shared library.
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/apps/Term/jni/Android.mk b/apps/Term/jni/Android.mk
new file mode 100644
index 0000000..2fe4a75
--- /dev/null
+++ b/apps/Term/jni/Android.mk
@@ -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.
+#
+
+# This makefile supplies the rules for building a library of JNI code for
+# use by our example of how to bundle a shared library with an APK.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := eng
+
+# This is the target being built.
+LOCAL_MODULE:= libterm
+
+
+# All of the source files that we will compile.
+LOCAL_SRC_FILES:= \
+  termExec.cpp
+
+# All of the shared libraries we link against.
+LOCAL_SHARED_LIBRARIES := \
+	libutils
+
+# No static libraries.
+LOCAL_STATIC_LIBRARIES :=
+
+# Also need the JNI headers.
+LOCAL_C_INCLUDES += \
+	$(JNI_H_INCLUDE)
+
+# No special compiler flags.
+LOCAL_CFLAGS +=
+
+# Don't prelink this library.  For more efficient code, you may want
+# to add this library to the prelink map and set this to true. However,
+# it's difficult to do this for applications that are not supplied as
+# part of a system image.
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apps/Term/jni/termExec.cpp b/apps/Term/jni/termExec.cpp
new file mode 100644
index 0000000..d0666cc
--- /dev/null
+++ b/apps/Term/jni/termExec.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "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>
+
+static jclass class_fileDescriptor;
+static jfieldID field_fileDescriptor_descriptor;
+static jmethodID method_fileDescriptor_init;
+
+
+class String8 {
+public:
+    String8() {
+        mString = 0;
+    }
+    
+    ~String8() {
+        if (mString) {
+            free(mString);
+        }
+    }
+
+    void set(const char16_t* o, size_t numChars) {
+        mString = (char*) malloc(numChars + 1);
+        for (size_t i = 0; i < numChars; i++) {
+            mString[i] = (char) o[i];
+        }
+        mString[numChars] = '\0';
+    }
+    
+    const char* string() {
+        return mString;
+    }
+private:
+    char* mString;
+};
+
+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){
+        close(ptm);
+
+        int pts;
+
+        setsid();
+        
+        pts = open(devname, O_RDWR);
+        if(pts < 0) exit(-1);
+
+        dup2(pts, 0);
+        dup2(pts, 1);
+        dup2(pts, 2);
+
+        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.set(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.set(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.set(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 = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor);
+
+    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 void android_os_Exec_close(JNIEnv *env, jobject clazz, jobject fileDescriptor)
+{
+    int fd;
+    struct winsize sz;
+
+    fd = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor);
+
+    if (env->ExceptionOccurred() != NULL) {
+        return;
+    }
+    
+    close(fd);
+}
+
+
+static int register_FileDescriptor(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 0;
+}
+
+
+static const char *classPathName = "com/android/term/Exec";
+
+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},
+    { "close", "(Ljava/io/FileDescriptor;)V",
+        (void*) android_os_Exec_close}
+};
+
+/*
+ * Register several native methods for one class.
+ */
+static int registerNativeMethods(JNIEnv* env, const char* className,
+    JNINativeMethod* gMethods, int numMethods)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(className);
+    if (clazz == NULL) {
+        LOGE("Native registration unable to find class '%s'", className);
+        return JNI_FALSE;
+    }
+    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+        LOGE("RegisterNatives failed for '%s'", className);
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+/*
+ * Register native methods for all classes we know about.
+ *
+ * returns JNI_TRUE on success.
+ */
+static int registerNatives(JNIEnv* env)
+{
+  if (!registerNativeMethods(env, classPathName, method_table, 
+                 sizeof(method_table) / sizeof(method_table[0]))) {
+    return JNI_FALSE;
+  }
+
+  return JNI_TRUE;
+}
+
+
+// ----------------------------------------------------------------------------
+
+/*
+ * This is called by the VM when the shared library is first loaded.
+ */
+ 
+typedef union {
+    JNIEnv* env;
+    void* venv;
+} UnionJNIEnvToVoid;
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+    UnionJNIEnvToVoid uenv;
+    uenv.venv = NULL;
+    jint result = -1;
+    JNIEnv* env = NULL;
+    
+    LOGI("JNI_OnLoad");
+
+    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
+        LOGE("ERROR: GetEnv failed");
+        goto bail;
+    }
+    env = uenv.env;
+    
+    if ((result = register_FileDescriptor(env)) < 0) {
+        LOGE("ERROR: registerFileDescriptor failed");
+        goto bail;
+    }
+
+    if (registerNatives(env) != JNI_TRUE) {
+        LOGE("ERROR: registerNatives failed");
+        goto bail;
+    }
+    
+    result = JNI_VERSION_1_4;
+    
+bail:
+    return result;
+}
diff --git a/apps/Term/src/com/android/term/Exec.java b/apps/Term/src/com/android/term/Exec.java
new file mode 100644
index 0000000..b53acfc
--- /dev/null
+++ b/apps/Term/src/com/android/term/Exec.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 com.android.term;
+
+import java.io.FileDescriptor;
+
+/**
+ * Utility methods for creating and managing a subprocess.
+ * <p>
+ * Note: The native methods access a package-private
+ * java.io.FileDescriptor field to get and set the raw Linux
+ * file descriptor. This might break if the implementation of
+ * java.io.FileDescriptor is changed.
+ */
+
+public class Exec
+{
+    static {
+        System.loadLibrary("term");
+    }
+
+    /**
+     * Create a subprocess. Differs from java.lang.ProcessBuilder in
+     * that a pty is used to communicate with the subprocess.
+     * <p>
+     * Callers are responsible for calling Exec.close() on the returned
+     * file descriptor.
+     *
+     * @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);
+        
+    /**
+     * Set the widow size for a given pty. Allows programs
+     * connected to the pty learn how large their screen is.
+     */
+    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);
+
+    /**
+     * Close a given file descriptor.
+     */
+    public static native void close(FileDescriptor fd);
+}
+
diff --git a/apps/Term/src/com/android/term/Term.java b/apps/Term/src/com/android/term/Term.java
index 34cd7e1..6041baf 100644
--- a/apps/Term/src/com/android/term/Term.java
+++ b/apps/Term/src/com/android/term/Term.java
@@ -16,6 +16,12 @@
 
 package com.android.term;
 
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
@@ -35,7 +41,6 @@
 import android.graphics.Typeface;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Exec;
 import android.os.Handler;
 import android.os.Message;
 import android.preference.PreferenceManager;
@@ -54,12 +59,6 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
 /**
  * A terminal emulator activity.
  */
@@ -96,10 +95,7 @@
 
     /**
      * The pseudo-teletype (pty) file descriptor that we use to communicate with
-     * another process, typically a shell. Currently we just use this to get the
-     * mTermIn / mTermOut file descriptors, but when we implement resizing of
-     * the terminal we will need it to issue the ioctl to inform the other
-     * process that we've changed the terminal size.
+     * another process, typically a shell.
      */
     private FileDescriptor mTermFd;
 
@@ -188,6 +184,15 @@
         updatePrefs();
     }
 
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mTermFd != null) {
+            Exec.close(mTermFd);
+            mTermFd = null;
+        }
+    }
+
     private void startListening() {
         int[] processId = new int[1];
 
diff --git a/build/Android.mk b/build/Android.mk
index cba96c6..d804ce7 100644
--- a/build/Android.mk
+++ b/build/Android.mk
@@ -4,7 +4,7 @@
 # anywhere else, and the rules don't support.  Aditionally, the depenencies on
 # these files don't really matter, because they are all generated as part of
 # building the docs.  So for the dependency, we just use the
-# offline-sdk-timestamp file, which is the $@ of the droiddoc rule.
+# api-stubs-timestamp file, which is the $@ of the droiddoc rule.
 # We also need to depend on framework-res.apk, in order to pull the
 # resource files out of there for aapt.
 #
@@ -22,7 +22,7 @@
 $(full_target): PRIVATE_CLASS_INTERMEDIATES_DIR := $(classes_dir)
 $(full_target): PRIVATE_FRAMEWORK_RES_PACKAGE := $(framework_res_package)
 
-$(full_target): $(OUT_DOCS)/offline-sdk-timestamp $(framework_res_package)
+$(full_target): $(OUT_DOCS)/api-stubs-timestamp $(framework_res_package)
 	@echo Compiling SDK Stubs: $@
 	$(hide) rm -rf $(PRIVATE_CLASS_INTERMEDIATES_DIR)
 	$(hide) mkdir -p $(PRIVATE_CLASS_INTERMEDIATES_DIR)
diff --git a/build/tools/make_windows_sdk.sh b/build/tools/make_windows_sdk.sh
index 68e6c80..aa68e17 100755
--- a/build/tools/make_windows_sdk.sh
+++ b/build/tools/make_windows_sdk.sh
@@ -1,11 +1,17 @@
 #!/bin/bash
 # Quick semi-auto file to build Windows SDK tools.
 #
-# Limitations:
+# Limitations and requirements:
 # - Expects the emulator has been built first, will pick it up from prebuilt.
 # - Run in Cygwin
-# - Needs Cygwin package zip
 # - Expects to have one of the existing SDK (Darwin or Linux) to build the Windows one
+# - Needs Cygwin packages: autoconf, bison, curl, flex, gcc, g++, git,
+#   gnupg, make, mingw-zlib, python, zip, unzip
+# - Must NOT have cygwin package readline (its GPL license might taint the SDK if
+#   it gets compiled in)
+# - Does not need a Java Development Kit or any other tools outside of cygwin.
+# - If you think you may have Windows versions of tools (e.g. make) installed, it may
+#   reduce confusion levels to 'export PATH=/usr/bin'
 
 set -e  # Fail this script as soon as a command fails -- fail early, fail fast
 
@@ -60,6 +66,10 @@
         # SDK number if you want, but not after, e.g these are valid:
         #    android_sdk_4242_platform.zip or blah_42_.zip
         #
+        # Note that the root directory name in the zip must match the zip
+        # name, too, so there's no point just changing the zip name to match
+        # the above format.
+        #
         # SDK_NUMBER will be empty if nothing matched.
         filename=`basename "$SDK_ZIP"`
         SDK_NUMBER=`echo $filename | sed -n 's/^.*_\([^_./]\+\)_[^_.]*\..*$/\1/p'`
diff --git a/emulator/keymaps/AVRCP.kl b/emulator/keymaps/AVRCP.kl
index d0eba10..aeb3c8a 100644
--- a/emulator/keymaps/AVRCP.kl
+++ b/emulator/keymaps/AVRCP.kl
@@ -1,6 +1,7 @@
-key 164   MEDIA_PLAY_PAUSE    WAKE
-key 128   MEDIA_STOP          WAKE
+key 200   MEDIA_PLAY_PAUSE    WAKE
+key 201   MEDIA_PLAY_PAUSE    WAKE
+key 166   MEDIA_STOP          WAKE
 key 163   MEDIA_NEXT          WAKE
 key 165   MEDIA_PREVIOUS      WAKE
 key 168   MEDIA_REWIND        WAKE
-key 159   MEDIA_FAST_FORWARD  WAKE
+key 208   MEDIA_FAST_FORWARD  WAKE
diff --git a/emulator/qtools/dmtrace.cpp b/emulator/qtools/dmtrace.cpp
index 6d9250a..c486c5f 100644
--- a/emulator/qtools/dmtrace.cpp
+++ b/emulator/qtools/dmtrace.cpp
@@ -5,6 +5,7 @@
 #include <unistd.h>
 #include <inttypes.h>
 #include <string.h>
+#include <unistd.h>
 #include "dmtrace.h"
 
 static const short kVersion = 2;
@@ -163,7 +164,7 @@
     //   sig = "()I"
 
     // Find the first parenthesis, the start of the signature.
-    char *paren = strchr(name, '(');
+    char *paren = (char*)strchr(name, '(');
 
     // If not found, then add the original name.
     if (paren == NULL) {
@@ -180,7 +181,7 @@
     *paren = 0;
 
     // Search for the last period, the start of the method name
-    char *dot = strrchr(name, '.');
+    char *dot = (char*)strrchr(name, '.');
 
     // If not found, then add the original name.
     if (dot == NULL || dot == name) {
diff --git a/emulator/qtools/trace_reader.cpp b/emulator/qtools/trace_reader.cpp
index d2af64f..47b5d93 100644
--- a/emulator/qtools/trace_reader.cpp
+++ b/emulator/qtools/trace_reader.cpp
@@ -1009,10 +1009,10 @@
 // be freed by the caller after it is no longer needed.
 static char *ExtractDexPathFromMmap(const char *mmap_path)
 {
-    char *end = rindex(mmap_path, '@');
+    const char *end = rindex(mmap_path, '@');
     if (end == NULL)
         return NULL;
-    char *start = rindex(mmap_path, '/');
+    const char *start = rindex(mmap_path, '/');
     if (start == NULL)
         return NULL;
     int len = end - start;
diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath
index 71e8d29..96797e7 100644
--- a/ide/eclipse/.classpath
+++ b/ide/eclipse/.classpath
@@ -28,8 +28,6 @@
 	<classpathentry kind="src" path="packages/providers/ImProvider/src"/>
 	<classpathentry kind="src" path="packages/providers/MediaProvider/src"/>
 	<classpathentry kind="src" path="packages/providers/TelephonyProvider/src"/>
-	<classpathentry kind="src" path="vendor/google/apps/Street/src"/>
-	<classpathentry kind="src" path="vendor/google/apps/YouTube/src"/>
 	<classpathentry kind="src" path="frameworks/base/awt"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/am/src"/>
 	<classpathentry kind="src" path="frameworks/base/cmds/input/src"/>
@@ -39,6 +37,7 @@
 	<classpathentry kind="src" path="frameworks/base/core/config/sdk"/>
 	<classpathentry kind="src" path="frameworks/base/graphics/java"/>
 	<classpathentry kind="src" path="frameworks/base/im/java"/>
+	<classpathentry kind="src" path="frameworks/base/keystore/java"/>
 	<classpathentry kind="src" path="frameworks/base/location/java"/>
 	<classpathentry kind="src" path="frameworks/base/media/java"/>
 	<classpathentry kind="src" path="frameworks/base/opengl/java"/>
@@ -49,6 +48,7 @@
 	<classpathentry kind="src" path="frameworks/base/telephony/java"/>
 	<classpathentry kind="src" path="frameworks/base/test-runner"/>
 	<classpathentry kind="src" path="frameworks/base/tts/java"/>
+	<classpathentry kind="src" path="frameworks/base/vpn/java"/>
 	<classpathentry kind="src" path="frameworks/base/wifi/java"/>
 	<classpathentry kind="src" path="frameworks/policies/base/phone"/>
 	<classpathentry kind="src" path="development/samples/ApiDemos/src"/>
@@ -94,7 +94,7 @@
 	<classpathentry kind="src" path="dalvik/libcore/x-net/src/main/java"/>
 	<classpathentry kind="src" path="dalvik/libcore/xml/src/main/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/ApiDemos_intermediates/src/src"/>
-	<classpathentry kind="src" path="out/target/common/obj/APPS/Browser_intermediates/src/src"/>
+	<classpathentry kind="src" path="out/target/common/obj/APPS/Email_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/IM_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Music_intermediates/src/src"/>
 	<classpathentry kind="src" path="out/target/common/obj/APPS/Phone_intermediates/src/src"/>
@@ -104,7 +104,7 @@
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/location/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java"/>
-	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/tts/java"/>
+	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/vpn/java"/>
 	<classpathentry kind="src" path="out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/wifi/java"/>
 	<classpathentry kind="src" path="out/target/common/R"/>
 	<classpathentry kind="src" path="external/tagsoup/src"/>
@@ -114,6 +114,6 @@
 	<classpathentry kind="lib" path="external/googleclient/googleclient-lib.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/google-framework_intermediates/javalib.jar"/>
 	<classpathentry kind="lib" path="out/target/common/obj/JAVA_LIBRARIES/googlelogin-client_intermediates/javalib.jar"/>
-	<classpathentry kind="lib" path="packages/apps/Calculator/arity-1.3.1.jar"/>
+	<classpathentry kind="lib" path="packages/apps/Calculator/arity-1.3.3.jar"/>
 	<classpathentry kind="output" path="out/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/classes"/>
 </classpath>
diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index 032f5c2..1e5de05 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -72,6 +72,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.WallpaperActivity"
+                android:label="@string/activity_wallpaper"
+                android:theme="@style/Theme.Wallpaper">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.TranslucentActivity"
                 android:label="@string/activity_translucent"
                 android:theme="@style/Theme.Translucent">
@@ -1562,6 +1571,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".graphics.ColorFilters" android:label="Graphics/ColorFilters">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".graphics.CreateBitmap" android:label="Graphics/CreateBitmap">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/samples/ApiDemos/res/drawable/btn_check_off.png b/samples/ApiDemos/res/drawable/btn_check_off.png
new file mode 100644
index 0000000..56d3861
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_check_off.png
Binary files differ
diff --git a/samples/ApiDemos/res/drawable/btn_check_on.png b/samples/ApiDemos/res/drawable/btn_check_on.png
new file mode 100644
index 0000000..791ac1d
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_check_on.png
Binary files differ
diff --git a/samples/ApiDemos/res/drawable/btn_circle_normal.png b/samples/ApiDemos/res/drawable/btn_circle_normal.png
new file mode 100644
index 0000000..fc5af1c
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_circle_normal.png
Binary files differ
diff --git a/samples/ApiDemos/res/drawable/btn_default_normal.9.png b/samples/ApiDemos/res/drawable/btn_default_normal.9.png
new file mode 100644
index 0000000..a2d5ccd
--- /dev/null
+++ b/samples/ApiDemos/res/drawable/btn_default_normal.9.png
Binary files differ
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index c2c9829..fa4e61c 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -38,6 +38,8 @@
             custom Theme.Dialog theme to make an activity that looks like a
             customized dialog, here with an ugly frame.</string>
 
+    <string name="activity_wallpaper">App/Activity/Wallpaper</string>
+    
     <string name="activity_translucent">App/Activity/Translucent</string>
     <string name="translucent_background">Example of how you can make an
             activity have a translucent background, compositing over
diff --git a/samples/ApiDemos/res/values/styles.xml b/samples/ApiDemos/res/values/styles.xml
index 3c9c681..8cc8312 100644
--- a/samples/ApiDemos/res/values/styles.xml
+++ b/samples/ApiDemos/res/values/styles.xml
@@ -37,9 +37,16 @@
         <item name="android:windowBackground">@drawable/filled_box</item>
     </style>
 
+    <!-- A theme that has a wallpaper background.  Here we explicitly specify
+         that this theme is to inherit from the system's wallpaper theme,
+         which sets up various attributes correctly. -->
+    <style name="Theme.Wallpaper" parent="android:style/Theme.Wallpaper">
+        <item name="android:colorForeground">#fff</item>
+    </style>
+
     <!-- A theme that has a translucent background.  Here we explicitly specify
          that this theme is to inherit from the system's translucent theme,
-         which sets up various attributes correctly.. -->
+         which sets up various attributes correctly. -->
     <style name="Theme.Translucent" parent="android:style/Theme.Translucent">
         <item name="android:windowBackground">@drawable/translucent_background</item>
         <item name="android:windowNoTitle">true</item>
diff --git a/samples/ApiDemos/src/com/example/android/apis/app/WallpaperActivity.java b/samples/ApiDemos/src/com/example/android/apis/app/WallpaperActivity.java
new file mode 100644
index 0000000..8d7f5a1
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/app/WallpaperActivity.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.example.android.apis.app;
+
+// Need the following import to get access to the app resources, since this
+// class is in a sub-package.
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+/**
+ * <h3>Wallpaper Activity</h3>
+ * 
+ * <p>This demonstrates the how to write an activity that has the system
+ * wallpaper behind it.</p>
+ */
+public class WallpaperActivity extends Activity {
+    /**
+     * Initialization of the Activity after it is first created.  Must at least
+     * call {@link android.app.Activity#setContentView setContentView()} to
+     * describe what is to be displayed in the screen.
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+        
+        // See assets/res/any/layout/translucent_background.xml for this
+        // view layout definition, which is being set here as
+        // the content of our screen.
+        setContentView(R.layout.translucent_background);
+    }
+}
diff --git a/samples/ApiDemos/src/com/example/android/apis/graphics/ColorFilters.java b/samples/ApiDemos/src/com/example/android/apis/graphics/ColorFilters.java
new file mode 100644
index 0000000..92d18ba
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/graphics/ColorFilters.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.apis.graphics;
+
+import com.example.android.apis.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.*;
+import android.graphics.drawable.*;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.*;
+
+public class ColorFilters extends GraphicsActivity {
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new SampleView(this));
+        
+    }
+    
+    private static class SampleView extends View {
+        private Activity mActivity;
+        private Drawable mDrawable;
+        private Drawable[] mDrawables;
+        private Paint mPaint;
+        private Paint mPaint2;
+        private float mPaintTextOffset;
+        private int[] mColors;
+        private PorterDuff.Mode[] mModes;
+        private int mModeIndex;
+
+        private static void addToTheRight(Drawable curr, Drawable prev) {
+            Rect r = prev.getBounds();
+            int x = r.right + 12;
+            int center = (r.top + r.bottom) >> 1;
+            int h = curr.getIntrinsicHeight();
+            int y = center - (h >> 1);
+            
+            curr.setBounds(x, y, x + curr.getIntrinsicWidth(), y + h);
+        }
+
+        public SampleView(Activity activity) {
+            super(activity);
+            mActivity = activity;
+            Context context = activity;
+            setFocusable(true);
+            
+            mDrawable = context.getResources().getDrawable(R.drawable.btn_default_normal);
+            mDrawable.setBounds(0, 0, 150, 48);
+            mDrawable.setDither(true);
+
+            int[] resIDs = new int[] {
+                R.drawable.btn_circle_normal,
+                R.drawable.btn_check_off,
+                R.drawable.btn_check_on
+            };
+            mDrawables = new Drawable[resIDs.length];
+            Drawable prev = mDrawable;
+            for (int i = 0; i < resIDs.length; i++) {
+                mDrawables[i] = context.getResources().getDrawable(resIDs[i]);
+                mDrawables[i].setDither(true);
+                addToTheRight(mDrawables[i], prev);
+                prev = mDrawables[i];
+            }
+
+            mPaint = new Paint();
+            mPaint.setAntiAlias(true);
+            mPaint.setTextSize(16);
+            mPaint.setTextAlign(Paint.Align.CENTER);
+            
+            mPaint2 = new Paint(mPaint);
+            mPaint2.setAlpha(64);
+            
+            Paint.FontMetrics fm = mPaint.getFontMetrics();
+            mPaintTextOffset = (fm.descent + fm.ascent) * 0.5f;
+            
+            mColors = new int[] {
+                0,
+                0xCC0000FF,
+                0x880000FF,
+                0x440000FF,
+                0xFFCCCCFF,
+                0xFF8888FF,
+                0xFF4444FF,
+            };
+
+            mModes = new PorterDuff.Mode[] {
+                PorterDuff.Mode.SRC_ATOP,
+                PorterDuff.Mode.MULTIPLY,
+            };
+            mModeIndex = 0;
+            
+            updateTitle();
+        }
+        
+        private void swapPaintColors() {
+            if (mPaint.getColor() == 0xFF000000) {
+                mPaint.setColor(0xFFFFFFFF);
+                mPaint2.setColor(0xFF000000);
+            } else {
+                mPaint.setColor(0xFF000000);
+                mPaint2.setColor(0xFFFFFFFF);
+            }
+            mPaint2.setAlpha(64);
+        }
+        
+        private void updateTitle() {
+            mActivity.setTitle(mModes[mModeIndex].toString());
+        }
+        
+        private void drawSample(Canvas canvas, ColorFilter filter) {
+            Rect r = mDrawable.getBounds();
+            float x = (r.left + r.right) * 0.5f;
+            float y = (r.top + r.bottom) * 0.5f - mPaintTextOffset;
+
+            mDrawable.setColorFilter(filter);
+            mDrawable.draw(canvas);
+            canvas.drawText("Label", x+1, y+1, mPaint2);
+            canvas.drawText("Label", x, y, mPaint);
+            
+            for (Drawable dr : mDrawables) {
+                dr.setColorFilter(filter);
+                dr.draw(canvas);
+            }
+        }
+        
+        @Override protected void onDraw(Canvas canvas) {
+            canvas.drawColor(0xFFCCCCCC);            
+
+            canvas.translate(8, 12);
+            for (int color : mColors) {
+                ColorFilter filter;
+                if (color == 0) {
+                    filter = null;
+                } else {
+                    filter = new PorterDuffColorFilter(color,
+                                                       mModes[mModeIndex]);
+                }
+                drawSample(canvas, filter);
+                canvas.translate(0, 55);
+            }
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    break;
+                case MotionEvent.ACTION_UP:
+                    // update mode every other time we change paint colors
+                    if (mPaint.getColor() == 0xFFFFFFFF) {
+                        mModeIndex = (mModeIndex + 1) % mModes.length;
+                        updateTitle();
+                    }
+                    swapPaintColors();
+                    invalidate();
+                    break;
+            }
+            return true;
+        }
+    }
+}
+
diff --git a/samples/BrowserPlugin/Android.mk b/samples/BrowserPlugin/Android.mk
new file mode 100644
index 0000000..16047d5
--- /dev/null
+++ b/samples/BrowserPlugin/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build application
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SampleBrowserPlugin
+
+LOCAL_JNI_SHARED_LIBRARIES := libsampleplugin
+
+include $(BUILD_PACKAGE)
+
+# ============================================================
+
+# Also build all of the sub-targets under this one: the shared library.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/samples/BrowserPlugin/AndroidManifest.xml b/samples/BrowserPlugin/AndroidManifest.xml
new file mode 100644
index 0000000..ae6b5db
--- /dev/null
+++ b/samples/BrowserPlugin/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.sampleplugin"
+      android:versionCode="1"
+      android:versionName="1.0">
+
+    <uses-permission android:name="android.webkit.permission.PLUGIN"/>
+
+    <uses-sdk android:minSdkVersion="3" />
+
+    <application android:icon="@drawable/sample_browser_plugin"
+                android:label="@string/sample_browser_plugin">
+        <service android:name="SamplePlugin">
+            <intent-filter>
+                <action android:name="android.webkit.PLUGIN" />
+            </intent-filter>
+        </service>
+    </application>
+
+</manifest>
diff --git a/samples/BrowserPlugin/MODULE_LICENSE_APACHE2 b/samples/BrowserPlugin/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/BrowserPlugin/MODULE_LICENSE_APACHE2
diff --git a/samples/BrowserPlugin/NOTICE b/samples/BrowserPlugin/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/samples/BrowserPlugin/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/samples/BrowserPlugin/README b/samples/BrowserPlugin/README
new file mode 100644
index 0000000..08b04a5
--- /dev/null
+++ b/samples/BrowserPlugin/README
@@ -0,0 +1,173 @@
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################
+######### CONTENTS ###########
+A. INTRODUCTION
+B. PLUGIN STRUCTURE
+C. HOW TO DEPLOY
+D. SUB-PLUGINS
+    1. ANIMATION
+    2. AUDIO
+    3. BACKGROUND
+    4. FORM
+    5. PAINT
+
+
+##############################
+## (A) INTRODUCTION ##########
+
+The sample plugin is intended to give plugin developers a point of reference to
+see how an android browser plugin is created and how to use the available APIs.
+A plugin is packaged like a standard apk and can be installed either via the 
+market or adb.  The sample plugin attempts to exercise as many of the APIs as
+possible but unfortunately not all are covered. 
+
+Trying to have a single plugin demonstrate all possible API interactions on one
+screen was not practical. On the other hand we also didn't want a separate
+plugin for each interction, as that would result in many separate apk's that
+would need to be maintained.  To solve this problem we developed the idea to use
+"sub-plugins". With a sub-plugin only one specific feature of the plugin would
+be active at a time, but they would all share as much common code as possible.
+A detailed description of each sub-plugin and its function can be found in the
+sub-plugins section.
+
+##############################
+## (B) PLUGIN STRUCTURE ######
+
+The sample plugin is packaged as one plugin but contains many unique sub-plugins
+(e.g. audio and paint).  The package consists of two pieces: (1) Java code
+containing the config; (2) C++ shared library containing the brower/plugin
+bindings and the sub-plugin classes.  
+
+~~~~ (1) JAVA ~~~~~
+Android.mk: specifies the name of the APK (SampleBrowserPlugin) as well as which
+            shared libraries to include.
+
+AndroidManifest.xml: similar to a standard android manifest file, except that it
+                     must contain the "uses-permission" and "intent-filter"
+                     elements that are plugin specific.
+
+src/*: location of the java files which in our case is just an empty service
+
+res/*: location of the static resources (e.g. an icon for the plugin)
+
+~~~~ (2) C++ ~~~~~
+jni/Android.mk: specifies the build parameters for the shared library that is to
+                be included with the apk. The library contains all the bindings
+                between the plugin and the browser.
+
+jni/main.*: this code is the binding point between the plugin and the browser.
+            It supports all of the functions required for a standard netscape
+            style plugin as well as all the android specific APIs. The initial
+            starting point for the plugin is the NP_Initialize function. The
+            NPP_New function is responsible for reading the input args and
+            selecting the appropriate sub-plugin to instantiate. Most other
+            functions either return fixed values or pass their inputs to the
+            sub-plugin for processing.
+
+jni/PluginObject.*: The pluginObject provides a convenient container in which to
+                    store variables (the plugin's state).  This objects two main
+                    responsibilities are (1) to construct and store the NPClass 
+                    object (done using code provided by Apple) and (2) provide
+                    the abstract class for the sub-plugin objects and a place to 
+                    store the sub-plugin after it is instantiated.  
+
+jni/*/*: Each of the sub-plugins has a folder that contains its class definition
+         and logic. The sub-plugin receives events from the browser and it can
+         also communicate with the browser using the netscape plugin functions
+         as well as the specialized android interfaces.
+
+
+##############################
+## (C) HOW TO DEPLOY #########
+
+To compile and install a plugin on a device/emulator simply...
+
+1. run "make SampleBrowserPlugin" (compiles libsampleplugin.so and builds the apk)
+2. the previous command produces an apk file so record its location
+3. run "adb install [apk_file]" to install it on a device/emulator
+4. the browser will auto recognize the plugin is available
+
+Now that the plugin is installed you can manage it just like you would any other
+application via Settings -> Applications -> Manage applications. To execute the
+plugin you need to include an html snippet (similar to the one found below) in
+a document that is accessible by the browser.  The mime-type cannot change but
+you can change the width, height, and parameters.  The parameters are used to
+notify the plugin which sub-plugin to execute and which drawing model to use.
+
+<object type="application/x-testbrowserplugin" height=50 width=250>
+    <param name="DrawingModel" value="Surface" />
+    <param name="PluginType" value="Background" />
+</object>
+
+
+##############################
+## (D) SUB-PLUGINS ###########
+
+Each sub-plugin corresponds to exactly one plugin type and can support one or
+more drawing models. In the subsections below there are descriptions of each of
+the sub-plugins as well as the information required to create the html snippets. 
+
+#######################
+## (D1) ANIMATION #####
+
+PLUGIN TYPE: Animation
+DRAWING MODEL: Bitmap
+
+This plugin draws a ball bouncing around the screen. If the plugin is not entirely
+on the screen and it it touched, then it will attempt to center itself on screen.
+
+#######################
+## (D2) AUDIO #########
+
+PLUGIN TYPE: Audio
+DRAWING MODEL: Bitmap
+
+This plugin plays a raw audio file located at /sdcard/sample.raw (need to supply
+your own). It uses touch to trigger the play, pause, and stop buttons.
+
+#######################
+## (D3) BACKGROUND ####
+
+PLUGIN TYPE: Background
+DRAWING MODEL: Surface
+
+This plugin has minimal visual components but mainly runs API tests in the 
+background. The  plugin handles scaling its own bitmap on zoom which in this
+case is a simple string of text. The output of this plugin is found in the logs
+as it prints errors if it detects any API failures. Some of the API's tested are
+timers, javascript access, and bitmap formatting.
+
+#######################
+## (D4) FORM ##########
+
+PLUGIN TYPE: Form
+DRAWING MODEL: Bitmap
+
+This plugin mimics a simple username/password form. You can select a textbox by
+either touching it or using the navigation keys.  Once selected the box will
+highlight and the keyboard will appear. If the textbox selected is not fully
+in view then the plugin will ensure it is centered on the screen.  
+
+#######################
+## (D5) PAINT #########
+
+PLUGIN TYPE: Paint
+DRAWING MODEL: Surface
+
+This plugin provides a surface that the user can "paint" on.  The inputs method
+can be toggled between mouse (dots) and touch (lines).  This plugin has a fixed
+surface and allows the browser to scale the surface when zooming. 
diff --git a/samples/BrowserPlugin/jni/Android.mk b/samples/BrowserPlugin/jni/Android.mk
new file mode 100644
index 0000000..3d1c88f
--- /dev/null
+++ b/samples/BrowserPlugin/jni/Android.mk
@@ -0,0 +1,59 @@
+##
+##
+## 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:
+##  * Redistributions of source code must retain the above copyright
+##    notice, this list of conditions and the following disclaimer.
+##  * 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.
+##
+## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+## CONTRIBUTORS 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.
+##
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	main.cpp \
+	PluginObject.cpp \
+	animation/AnimationPlugin.cpp \
+	audio/AudioPlugin.cpp \
+	background/BackgroundPlugin.cpp \
+	form/FormPlugin.cpp \
+	paint/PaintPlugin.cpp \
+
+LOCAL_C_INCLUDES += \
+	$(LOCAL_PATH) \
+	$(LOCAL_PATH)/animation \
+	$(LOCAL_PATH)/audio \
+	$(LOCAL_PATH)/background \
+	$(LOCAL_PATH)/form \
+	$(LOCAL_PATH)/paint \
+	external/webkit/WebCore/bridge \
+	external/webkit/WebCore/plugins \
+	external/webkit/WebCore/platform/android/JavaVM \
+	external/webkit/WebKit/android/plugins
+
+LOCAL_SRC_FILES := $(LOCAL_SRC_FILES)
+LOCAL_CFLAGS += -fvisibility=hidden 
+LOCAL_PRELINK_MODULE:=false
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+
+LOCAL_MODULE:= libsampleplugin
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/samples/BrowserPlugin/jni/PluginObject.cpp b/samples/BrowserPlugin/jni/PluginObject.cpp
new file mode 100644
index 0000000..80f5e7c
--- /dev/null
+++ b/samples/BrowserPlugin/jni/PluginObject.cpp
@@ -0,0 +1,177 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation,
+ modification or redistribution of this Apple software constitutes acceptance of these
+ terms.  If you do not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject to these
+ terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and
+ redistribute the Apple Software, with or without modifications, in source and/or binary
+ forms; provided that if you redistribute the Apple Software in its entirety and without
+ modifications, you must retain this notice and the following text and disclaimers in all
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks
+ or logos of Apple Computer, Inc. may be used to endorse or promote products derived from
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your
+ derivative works or by other works in which the Apple Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+          OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include "main.h"
+#include "PluginObject.h"
+
+static void pluginInvalidate(NPObject *obj);
+static bool pluginHasProperty(NPObject *obj, NPIdentifier name);
+static bool pluginHasMethod(NPObject *obj, NPIdentifier name);
+static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant);
+static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant);
+static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
+static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result);
+static NPObject *pluginAllocate(NPP npp, NPClass *theClass);
+static void pluginDeallocate(NPObject *obj);
+static bool pluginRemoveProperty(NPObject *npobj, NPIdentifier name);
+static bool pluginEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count);
+
+
+
+static NPClass pluginClass = {
+    NP_CLASS_STRUCT_VERSION,
+    pluginAllocate,
+    pluginDeallocate,
+    pluginInvalidate,
+    pluginHasMethod,
+    pluginInvoke,
+    pluginInvokeDefault,
+    pluginHasProperty,
+    pluginGetProperty,
+    pluginSetProperty,
+    pluginRemoveProperty,
+    pluginEnumerate
+};
+
+NPClass *getPluginClass(void)
+{
+    return &pluginClass;
+}
+
+static bool identifiersInitialized = false;
+
+#define ID_TESTFILE_PROPERTY            0
+#define NUM_PROPERTY_IDENTIFIERS        1
+
+static NPIdentifier pluginPropertyIdentifiers[NUM_PROPERTY_IDENTIFIERS];
+static const NPUTF8 *pluginPropertyIdentifierNames[NUM_PROPERTY_IDENTIFIERS] = {
+    "testfile"
+};
+
+#define ID_GETTESTFILE_METHOD                   0
+#define NUM_METHOD_IDENTIFIERS                  1
+
+static NPIdentifier pluginMethodIdentifiers[NUM_METHOD_IDENTIFIERS];
+static const NPUTF8 *pluginMethodIdentifierNames[NUM_METHOD_IDENTIFIERS] = {
+    "getTestFile"
+};
+
+static void initializeIdentifiers(void)
+{
+    browser->getstringidentifiers(pluginPropertyIdentifierNames, NUM_PROPERTY_IDENTIFIERS, pluginPropertyIdentifiers);
+    browser->getstringidentifiers(pluginMethodIdentifierNames, NUM_METHOD_IDENTIFIERS, pluginMethodIdentifiers);
+}
+
+static bool pluginHasProperty(NPObject *obj, NPIdentifier name)
+{
+    int i;
+    for (i = 0; i < NUM_PROPERTY_IDENTIFIERS; i++)
+        if (name == pluginPropertyIdentifiers[i])
+            return true;
+    return false;
+}
+
+static bool pluginHasMethod(NPObject *obj, NPIdentifier name)
+{
+    int i;
+    for (i = 0; i < NUM_METHOD_IDENTIFIERS; i++)
+        if (name == pluginMethodIdentifiers[i])
+            return true;
+    return false;
+}
+
+static bool pluginGetProperty(NPObject *obj, NPIdentifier name, NPVariant *variant)
+{
+    PluginObject *plugin = (PluginObject *)obj;
+    if (name == pluginPropertyIdentifiers[ID_TESTFILE_PROPERTY]) {
+        BOOLEAN_TO_NPVARIANT(true, *variant);
+        return true;
+    }
+    return false;
+}
+
+static bool pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant)
+{
+    return false;
+}
+
+static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result)
+{
+    PluginObject *plugin = (PluginObject *)obj;
+    if (name == pluginMethodIdentifiers[ID_GETTESTFILE_METHOD]) {
+        return true;
+    }
+    return false;
+}
+
+static bool pluginInvokeDefault(NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result)
+{
+    return false;
+}
+
+static void pluginInvalidate(NPObject *obj)
+{
+    // Release any remaining references to JavaScript objects.
+}
+
+static NPObject *pluginAllocate(NPP npp, NPClass *theClass)
+{
+    PluginObject *newInstance = (PluginObject*) malloc(sizeof(PluginObject));
+    newInstance->header._class = theClass;
+    newInstance->header.referenceCount = 1;
+
+    if (!identifiersInitialized) {
+        identifiersInitialized = true;
+        initializeIdentifiers();
+    }
+
+    newInstance->npp = npp;
+
+    return &newInstance->header;
+}
+
+static void pluginDeallocate(NPObject *obj)
+{
+    free(obj);
+}
+
+static bool pluginRemoveProperty(NPObject *npobj, NPIdentifier name)
+{
+    return false;
+}
+
+static bool pluginEnumerate(NPObject *npobj, NPIdentifier **value, uint32_t *count)
+{
+    return false;
+}
diff --git a/samples/BrowserPlugin/jni/PluginObject.h b/samples/BrowserPlugin/jni/PluginObject.h
new file mode 100644
index 0000000..82f6f48
--- /dev/null
+++ b/samples/BrowserPlugin/jni/PluginObject.h
@@ -0,0 +1,74 @@
+/*
+ IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in
+ consideration of your agreement to the following terms, and your use, installation,
+ modification or redistribution of this Apple software constitutes acceptance of these
+ terms.  If you do not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and subject to these
+ terms, Apple grants you a personal, non-exclusive license, under AppleÕs copyrights in
+ this original Apple software (the "Apple Software"), to use, reproduce, modify and
+ redistribute the Apple Software, with or without modifications, in source and/or binary
+ forms; provided that if you redistribute the Apple Software in its entirety and without
+ modifications, you must retain this notice and the following text and disclaimers in all
+ such redistributions of the Apple Software.  Neither the name, trademarks, service marks
+ or logos of Apple Computer, Inc. may be used to endorse or promote products derived from
+ the Apple Software without specific prior written permission from Apple. Except as expressly
+ stated in this notice, no other rights or licenses, express or implied, are granted by Apple
+ herein, including but not limited to any patent rights that may be infringed by your
+ derivative works or by other works in which the Apple Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
+ EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
+ USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+          OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
+ REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
+ WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
+ OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PluginObject__DEFINED
+#define PluginObject__DEFINED
+
+#include "main.h"
+
+class SubPlugin {
+public:
+    SubPlugin(NPP inst) : m_inst(inst) {}
+    virtual ~SubPlugin() {}
+    virtual int16 handleEvent(const ANPEvent* evt) = 0;
+    virtual bool supportsDrawingModel(ANPDrawingModel) = 0;
+
+    NPP inst() const { return m_inst; }
+
+private:
+    NPP m_inst;
+};
+
+enum PluginTypes {
+    kAnimation_PluginType  = 1,
+    kAudio_PluginType      = 2,
+    kBackground_PluginType = 3,
+    kForm_PluginType       = 4,
+    kText_PluginType       = 5,
+    kPaint_PluginType      = 6,
+};
+typedef uint32_t PluginType;
+
+typedef struct PluginObject {
+    NPObject header;
+    NPP npp;
+    NPWindow* window;
+
+    PluginType pluginType;
+    SubPlugin* activePlugin;
+
+} PluginObject;
+
+NPClass *getPluginClass(void);
+
+#endif // PluginObject__DEFINED
diff --git a/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp b/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp
new file mode 100644
index 0000000..b6175c1
--- /dev/null
+++ b/samples/BrowserPlugin/jni/animation/AnimationPlugin.cpp
@@ -0,0 +1,197 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "AnimationPlugin.h"
+
+#include <math.h>
+#include <string.h>
+
+extern NPNetscapeFuncs*        browser;
+extern ANPLogInterfaceV0       gLogI;
+extern ANPCanvasInterfaceV0    gCanvasI;
+extern ANPPaintInterfaceV0     gPaintI;
+extern ANPPathInterfaceV0      gPathI;
+extern ANPWindowInterfaceV0    gWindowI;
+
+static uint16 rnd16(float x, int inset) {
+    int ix = (int)roundf(x) + inset;
+    if (ix < 0) {
+        ix = 0;
+    }
+    return static_cast<uint16>(ix);
+}
+
+static void inval(NPP instance, const ANPRectF& r, bool doAA) {
+    const int inset = doAA ? -1 : 0;
+
+    NPRect inval;
+    inval.left = rnd16(r.left, inset);
+    inval.top = rnd16(r.top, inset);
+    inval.right = rnd16(r.right, -inset);
+    inval.bottom = rnd16(r.bottom, -inset);
+    browser->invalidaterect(instance, &inval);
+}
+
+static void bounce(float* x, float* dx, const float max) {
+    *x += *dx;
+    if (*x < 0) {
+        *x = 0;
+        if (*dx < 0) {
+            *dx = -*dx;
+        }
+    } else if (*x > max) {
+        *x = max;
+        if (*dx > 0) {
+            *dx = -*dx;
+        }
+    }
+}
+///////////////////////////////////////////////////////////////////////////////
+
+BallAnimation::BallAnimation(NPP inst) : SubPlugin(inst) {
+    m_x = m_y = 0;
+    m_dx = 7 * SCALE;
+    m_dy = 5 * SCALE;
+
+    memset(&m_oval, 0, sizeof(m_oval));
+
+    m_paint = gPaintI.newPaint();
+    gPaintI.setFlags(m_paint, gPaintI.getFlags(m_paint) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paint, 0xFFFF0000);
+
+    //register for touch events
+    ANPEventFlags flags = kTouch_ANPEventFlag;
+    NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
+    if (err != NPERR_NO_ERROR) {
+        gLogI.log(inst, kError_ANPLogType, "Error selecting input events.");
+    }
+}
+
+BallAnimation::~BallAnimation() {
+    gPaintI.deletePaint(m_paint);
+}
+
+bool BallAnimation::supportsDrawingModel(ANPDrawingModel model) {
+    return (model == kBitmap_ANPDrawingModel);
+}
+
+void BallAnimation::drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip) {
+
+    // create a canvas
+    ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
+
+    // clip the canvas
+    ANPRectF clipR;
+    clipR.left = clip.left;
+    clipR.top = clip.top;
+    clipR.right = clip.right;
+    clipR.bottom = clip.bottom;
+    gCanvasI.clipRect(canvas, &clipR);
+
+    // setup variables
+    PluginObject *obj = (PluginObject*) inst()->pdata;
+    const float OW = 20;
+    const float OH = 20;
+    const int W = obj->window->width;
+    const int H = obj->window->height;
+
+    // paint the canvas (using the path API)
+    gCanvasI.drawColor(canvas, 0xFFFFFFFF);
+    {
+        ANPPath* path = gPathI.newPath();
+
+        float cx = W * 0.5f;
+        float cy = H * 0.5f;
+        gPathI.moveTo(path, 0, 0);
+        gPathI.quadTo(path, cx, cy, W, 0);
+        gPathI.quadTo(path, cx, cy, W, H);
+        gPathI.quadTo(path, cx, cy, 0, H);
+        gPathI.quadTo(path, cx, cy, 0, 0);
+
+        gPaintI.setColor(m_paint, 0xFF0000FF);
+        gCanvasI.drawPath(canvas, path, m_paint);
+
+        ANPRectF bounds;
+        memset(&bounds, 0, sizeof(bounds));
+        gPathI.getBounds(path, &bounds);
+        gPathI.deletePath(path);
+    }
+
+    // draw the oval
+    inval(inst(), m_oval, true);  // inval the old
+    m_oval.left = m_x;
+    m_oval.top = m_y;
+    m_oval.right = m_x + OW;
+    m_oval.bottom = m_y + OH;
+    inval(inst(), m_oval, true);  // inval the new
+    gPaintI.setColor(m_paint, 0xFFFF0000);
+    gCanvasI.drawOval(canvas, &m_oval, m_paint);
+
+    // update the coordinates of the oval
+    bounce(&m_x, &m_dx, obj->window->width - OW);
+    bounce(&m_y, &m_dy, obj->window->height - OH);
+
+    // delete the canvas
+    gCanvasI.deleteCanvas(canvas);
+}
+
+void BallAnimation::showEntirePluginOnScreen() {
+    NPP instance = this->inst();
+    PluginObject *obj = (PluginObject*) instance->pdata;
+    NPWindow *window = obj->window;
+
+    ANPRectI visibleRects[1];
+
+    visibleRects[0].left = 0;
+    visibleRects[0].top = 0;
+    visibleRects[0].right = window->width;
+    visibleRects[0].bottom = window->height;
+
+    gWindowI.setVisibleRects(instance, visibleRects, 1);
+    gWindowI.clearVisibleRects(instance);
+}
+
+int16 BallAnimation::handleEvent(const ANPEvent* evt) {
+    NPP instance = this->inst();
+
+    switch (evt->eventType) {
+        case kDraw_ANPEventType:
+            switch (evt->data.draw.model) {
+                case kBitmap_ANPDrawingModel:
+                    drawPlugin(evt->data.draw.data.bitmap, evt->data.draw.clip);
+                    return 1;
+                default:
+                    break;   // unknown drawing model
+            }
+        case kTouch_ANPEventType:
+             if (kDown_ANPTouchAction == evt->data.touch.action) {
+                 showEntirePluginOnScreen();
+             }
+             return 1;
+        default:
+            break;
+    }
+    return 0;   // unknown or unhandled event
+}
diff --git a/samples/BrowserPlugin/jni/animation/AnimationPlugin.h b/samples/BrowserPlugin/jni/animation/AnimationPlugin.h
new file mode 100644
index 0000000..ef2a3f5
--- /dev/null
+++ b/samples/BrowserPlugin/jni/animation/AnimationPlugin.h
@@ -0,0 +1,52 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "PluginObject.h"
+
+#ifndef pluginGraphics__DEFINED
+#define pluginGraphics__DEFINED
+
+class BallAnimation : public SubPlugin {
+public:
+    BallAnimation(NPP inst);
+    virtual ~BallAnimation();
+    virtual bool supportsDrawingModel(ANPDrawingModel);
+    virtual int16 handleEvent(const ANPEvent* evt);
+private:
+    void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
+    void showEntirePluginOnScreen();
+
+    float m_x;
+    float m_y;
+    float m_dx;
+    float m_dy;
+
+    ANPRectF    m_oval;
+    ANPPaint*   m_paint;
+
+    static const float SCALE = 0.1;
+};
+
+#endif // pluginGraphics__DEFINED
diff --git a/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp b/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp
new file mode 100644
index 0000000..58d340c
--- /dev/null
+++ b/samples/BrowserPlugin/jni/audio/AudioPlugin.cpp
@@ -0,0 +1,382 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "AudioPlugin.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+
+extern NPNetscapeFuncs*         browser;
+extern ANPLogInterfaceV0        gLogI;
+extern ANPCanvasInterfaceV0     gCanvasI;
+extern ANPPaintInterfaceV0      gPaintI;
+extern ANPAudioTrackInterfaceV0 gSoundI;
+extern ANPTypefaceInterfaceV0   gTypefaceI;
+
+
+static void inval(NPP instance) {
+    browser->invalidaterect(instance, NULL);
+}
+
+static uint16 rnd16(float x, int inset) {
+    int ix = (int)roundf(x) + inset;
+    if (ix < 0) {
+        ix = 0;
+    }
+    return static_cast<uint16>(ix);
+}
+
+static void inval(NPP instance, const ANPRectF& r, bool doAA) {
+    const int inset = doAA ? -1 : 0;
+
+    PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata);
+    NPRect inval;
+    inval.left = rnd16(r.left, inset);
+    inval.top = rnd16(r.top, inset);
+    inval.right = rnd16(r.right, -inset);
+    inval.bottom = rnd16(r.bottom, -inset);
+    browser->invalidaterect(instance, &inval);
+}
+
+static void audioCallback(ANPAudioEvent evt, void* user, ANPAudioBuffer* buffer) {
+    switch (evt) {
+        case kMoreData_ANPAudioEvent: {
+            SoundPlay* play = reinterpret_cast<SoundPlay*>(user);
+            size_t amount = fread(buffer->bufferData, 1, buffer->size, play->file);
+            buffer->size = amount;
+            if (amount == 0) {
+                gSoundI.stop(play->track);
+                fclose(play->file);
+                play->file = NULL;
+                // TODO need to notify our main thread to delete the track now
+            }
+
+            if (play->fileSize > 0) {
+                // TODO we need to properly update the progress value
+                play->progress = 1;
+                inval(play->instance);
+            }
+
+
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AudioPlugin::AudioPlugin(NPP inst) : SubPlugin(inst) {
+
+    const char path[] = "/sdcard/sample.raw";
+
+    // open a file stream
+    FILE* f = fopen(path, "r");
+    gLogI.log(inst, kDebug_ANPLogType, "--- path %s FILE %p", path, f);
+
+    // setup our private audio struct's default values
+    m_soundPlay = new SoundPlay;
+    m_soundPlay->instance = inst;
+    m_soundPlay->progress = 0;
+    m_soundPlay->fileSize = 0;
+    m_soundPlay->file = f;
+    m_soundPlay->track = NULL;
+
+    // create the audio track
+    if (f) {
+        m_soundPlay->track = gSoundI.newTrack(44100, kPCM16Bit_ANPSampleFormat, 2, audioCallback, m_soundPlay);
+        if (!m_soundPlay->track) {
+            fclose(f);
+            m_soundPlay->file = NULL;
+        }
+    }
+
+    // get the audio file's size
+    int fileDescriptor = open(path, O_RDONLY);
+    struct stat fileStatus;
+
+    if(fileDescriptor <= 0) {
+        gLogI.log(inst, kError_ANPLogType, "fopen error");
+    }
+    else if (fstat(fileDescriptor, &fileStatus) != 0) {
+        gLogI.log(inst, kDebug_ANPLogType, "File Size: %d", fileStatus.st_size);
+        m_soundPlay->fileSize = fileStatus.st_size;
+    } else {
+        gLogI.log(inst, kError_ANPLogType, "fstat error");
+    }
+
+    // configure the UI elements
+    m_activeTouch = false;
+
+    memset(&m_trackRect, 0, sizeof(m_trackRect));
+    memset(&m_playRect,  0, sizeof(m_playRect));
+    memset(&m_pauseRect, 0, sizeof(m_pauseRect));
+    memset(&m_stopRect,  0, sizeof(m_stopRect));
+
+    m_paintTrack = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintTrack, gPaintI.getFlags(m_paintTrack) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintTrack, 0xFFC0C0C0);
+
+    m_paintRect = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintRect, gPaintI.getFlags(m_paintRect) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintRect, 0xFFA8A8A8);
+
+    m_paintText = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintText, gPaintI.getFlags(m_paintText) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintText, 0xFF2F4F4F);
+    gPaintI.setTextSize(m_paintText, 18);
+
+    m_paintTrackProgress = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintTrackProgress, gPaintI.getFlags(m_paintTrackProgress) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintTrackProgress, 0xFF545454);
+
+    m_paintActiveRect = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintActiveRect, gPaintI.getFlags(m_paintActiveRect) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintActiveRect, 0xFF545454);
+
+    ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
+    gPaintI.setTypeface(m_paintText, tf);
+    gTypefaceI.unref(tf);
+
+    //register for touch events
+    ANPEventFlags flags = kTouch_ANPEventFlag;
+    NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
+    if (err != NPERR_NO_ERROR) {
+        gLogI.log(inst, kError_ANPLogType, "Error selecting input events.");
+    }
+}
+
+AudioPlugin::~AudioPlugin() {
+    gPaintI.deletePaint(m_paintTrack);
+    gPaintI.deletePaint(m_paintRect);
+    gPaintI.deletePaint(m_paintText);
+    gPaintI.deletePaint(m_paintTrackProgress);
+    gPaintI.deletePaint(m_paintActiveRect);
+    if(m_soundPlay->track)
+        gSoundI.deleteTrack(m_soundPlay->track);
+    delete m_soundPlay;
+}
+
+bool AudioPlugin::supportsDrawingModel(ANPDrawingModel model) {
+    return (model == kBitmap_ANPDrawingModel);
+}
+
+void AudioPlugin::drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip) {
+    ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
+
+    ANPRectF clipR;
+    clipR.left = clip.left;
+    clipR.top = clip.top;
+    clipR.right = clip.right;
+    clipR.bottom = clip.bottom;
+    gCanvasI.clipRect(canvas, &clipR);
+
+    draw(canvas);
+    gCanvasI.deleteCanvas(canvas);
+}
+
+void AudioPlugin::draw(ANPCanvas* canvas) {
+    NPP instance = this->inst();
+    PluginObject *obj = (PluginObject*) instance->pdata;
+
+    gLogI.log(instance, kError_ANPLogType, "Drawing");
+
+    const float trackHeight = 30;
+    const float buttonWidth = 60;
+    const float buttonHeight = 30;
+    const int W = obj->window->width;
+    const int H = obj->window->height;
+
+    // color the plugin canvas
+    gCanvasI.drawColor(canvas, 0xFFCDCDCD);
+
+    // get font metrics
+    ANPFontMetrics fontMetrics;
+    gPaintI.getFontMetrics(m_paintText, &fontMetrics);
+
+    // draw the track box (1 px from the edge)
+    m_trackRect.left = 1;
+    m_trackRect.top = 1;
+    m_trackRect.right = W - 2;
+    m_trackRect.bottom = 1 + trackHeight;
+    gCanvasI.drawRect(canvas, &m_trackRect, m_paintTrack);
+
+    // draw the progress bar
+    if (m_soundPlay->progress > 0) {
+        // TODO need to draw progress bar to cover the proper percentage of the track bar
+        gCanvasI.drawRect(canvas, &m_trackRect, m_paintTrackProgress);
+    }
+
+    // draw the play box (under track box)
+    m_playRect.left = m_trackRect.left + 5;
+    m_playRect.top = m_trackRect.bottom + 10;
+    m_playRect.right = m_playRect.left + buttonWidth;
+    m_playRect.bottom = m_playRect.top + buttonHeight;
+    gCanvasI.drawRect(canvas, &m_playRect, getPaint(&m_playRect));
+    // draw the play box (under track box)
+    const char playText[] = "Play";
+    gCanvasI.drawText(canvas, playText, sizeof(playText)-1, m_playRect.left + 5,
+                      m_playRect.top - fontMetrics.fTop, m_paintText);
+
+    // draw the pause box (under track box)
+    m_pauseRect.left = m_playRect.right + 20;
+    m_pauseRect.top = m_trackRect.bottom + 10;
+    m_pauseRect.right = m_pauseRect.left + buttonWidth;
+    m_pauseRect.bottom = m_pauseRect.top + buttonHeight;
+    gCanvasI.drawRect(canvas, &m_pauseRect, getPaint(&m_pauseRect));
+    // draw the text in the pause box
+    const char pauseText[] = "Pause";
+    gCanvasI.drawText(canvas, pauseText, sizeof(pauseText)-1, m_pauseRect.left + 5,
+                      m_pauseRect.top - fontMetrics.fTop, m_paintText);
+
+    // draw the stop box (under track box)
+    m_stopRect.left = m_pauseRect.right + 20;
+    m_stopRect.top = m_trackRect.bottom + 10;
+    m_stopRect.right = m_stopRect.left + buttonWidth;
+    m_stopRect.bottom = m_stopRect.top + buttonHeight;
+    gCanvasI.drawRect(canvas, &m_stopRect, getPaint(&m_stopRect));
+    // draw the text in the pause box
+    const char stopText[] = "Stop";
+    gCanvasI.drawText(canvas, stopText, sizeof(stopText)-1, m_stopRect.left + 5,
+                      m_stopRect.top - fontMetrics.fTop, m_paintText);
+}
+
+ANPPaint* AudioPlugin::getPaint(ANPRectF* input) {
+    return (input == m_activeRect) ? m_paintActiveRect : m_paintRect;
+}
+
+int16 AudioPlugin::handleEvent(const ANPEvent* evt) {
+    NPP instance = this->inst();
+
+    switch (evt->eventType) {
+        case kDraw_ANPEventType:
+            switch (evt->data.draw.model) {
+                case kBitmap_ANPDrawingModel:
+                    drawPlugin(evt->data.draw.data.bitmap, evt->data.draw.clip);
+                    return 1;
+                default:
+                    break;   // unknown drawing model
+            }
+
+        case kTouch_ANPEventType: {
+            int x = evt->data.touch.x;
+            int y = evt->data.touch.y;
+            if (kDown_ANPTouchAction == evt->data.touch.action) {
+
+                m_activeTouchRect = validTouch(x,y);
+                if(m_activeTouchRect) {
+                    m_activeTouch = true;
+                    return 1;
+                }
+
+            } else if (kUp_ANPTouchAction == evt->data.touch.action && m_activeTouch) {
+                handleTouch(x, y);
+                m_activeTouch = false;
+                return 1;
+            } else if (kCancel_ANPTouchAction == evt->data.touch.action) {
+                m_activeTouch = false;
+            }
+            break;
+        }
+        default:
+            break;
+    }
+    return 0;   // unknown or unhandled event
+}
+
+void AudioPlugin::invalActiveRect() {
+
+}
+
+ANPRectF* AudioPlugin::validTouch(int x, int y) {
+
+    if (m_playRect.left && x < m_playRect.right && y > m_playRect.top && y < m_playRect.bottom)
+        return &m_playRect;
+    else if (m_pauseRect.left && x < m_pauseRect.right && y > m_pauseRect.top && y < m_pauseRect.bottom)
+        return &m_pauseRect;
+    else if (x > m_stopRect.left && x < m_stopRect.right && y > m_stopRect.top && y < m_stopRect.bottom)
+        return &m_stopRect;
+    else
+        return NULL;
+}
+
+void AudioPlugin::handleTouch(int x, int y) {
+    NPP instance = this->inst();
+
+    // if the track is null then return
+    if (NULL == m_soundPlay->track) {
+        gLogI.log(instance, kError_ANPLogType, "---- %p unable to create track",
+                  instance);
+        return;
+    }
+
+    // check to make sure the currentRect matches the activeRect
+    ANPRectF* currentRect = validTouch(x,y);
+    if (m_activeTouchRect != currentRect)
+        return;
+
+    if (currentRect == &m_playRect) {
+
+        gLogI.log(instance, kDebug_ANPLogType, "---- %p starting track (%d)",
+                  m_soundPlay->track, gSoundI.isStopped(m_soundPlay->track));
+
+        if (gSoundI.isStopped(m_soundPlay->track)) {
+            gSoundI.start(m_soundPlay->track);
+        }
+    }
+    else if (currentRect == &m_pauseRect) {
+
+        gLogI.log(instance, kDebug_ANPLogType, "---- %p pausing track (%d)",
+                  m_soundPlay->track, gSoundI.isStopped(m_soundPlay->track));
+
+        if (!gSoundI.isStopped(m_soundPlay->track)) {
+            gSoundI.pause(m_soundPlay->track);
+        }
+    }
+    else if (currentRect == &m_stopRect) {
+
+        gLogI.log(instance, kDebug_ANPLogType, "---- %p stopping track (%d)",
+                  m_soundPlay->track, gSoundI.isStopped(m_soundPlay->track));
+
+        if (!gSoundI.isStopped(m_soundPlay->track)) {
+            gSoundI.stop(m_soundPlay->track);
+        }
+        if (m_soundPlay->file) {
+            fseek(m_soundPlay->file, 0, SEEK_SET);
+        }
+    }
+    else {
+        return;
+    }
+
+    // set the currentRect to be the activeRect
+    m_activeRect = currentRect;
+    inval(instance);
+}
diff --git a/samples/BrowserPlugin/jni/audio/AudioPlugin.h b/samples/BrowserPlugin/jni/audio/AudioPlugin.h
new file mode 100644
index 0000000..129d33a
--- /dev/null
+++ b/samples/BrowserPlugin/jni/audio/AudioPlugin.h
@@ -0,0 +1,74 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "PluginObject.h"
+#include <stdio.h>
+
+#ifndef audioPlugin__DEFINED
+#define audioPlugin__DEFINED
+
+struct SoundPlay {
+    NPP             instance;
+    ANPAudioTrack*  track;
+    FILE*           file;
+    int             fileSize;
+    int             progress; // value between 0 and 100
+};
+
+class AudioPlugin : public SubPlugin {
+public:
+    AudioPlugin(NPP inst);
+    virtual ~AudioPlugin();
+    virtual bool supportsDrawingModel(ANPDrawingModel);
+    virtual int16 handleEvent(const ANPEvent* evt);
+private:
+    void draw(ANPCanvas*);
+    void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
+
+    void handleTouch(int x, int y);
+    void invalActiveRect();
+    ANPPaint* getPaint(ANPRectF*);
+    ANPRectF* validTouch(int x, int y);
+
+    ANPRectF    m_trackRect;
+    ANPRectF    m_playRect;
+    ANPRectF    m_pauseRect;
+    ANPRectF    m_stopRect;
+
+    ANPPaint*   m_paintTrack;
+    ANPPaint*   m_paintRect;
+    ANPPaint*   m_paintText;
+
+    ANPPaint*   m_paintTrackProgress;
+    ANPPaint*   m_paintActiveRect;
+
+    SoundPlay*  m_soundPlay;
+
+    bool        m_activeTouch;
+    ANPRectF*   m_activeTouchRect;
+    ANPRectF*   m_activeRect;
+};
+
+#endif // audioPlugin__DEFINED
diff --git a/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
new file mode 100644
index 0000000..eac5db2
--- /dev/null
+++ b/samples/BrowserPlugin/jni/background/BackgroundPlugin.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "BackgroundPlugin.h"
+#include "android_npapi.h"
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+
+extern NPNetscapeFuncs*        browser;
+extern ANPBitmapInterfaceV0    gBitmapI;
+extern ANPCanvasInterfaceV0    gCanvasI;
+extern ANPLogInterfaceV0       gLogI;
+extern ANPPaintInterfaceV0     gPaintI;
+extern ANPSurfaceInterfaceV0   gSurfaceI;
+extern ANPTypefaceInterfaceV0  gTypefaceI;
+
+#define ARRAY_COUNT(array)      (sizeof(array) / sizeof(array[0]))
+
+static uint32_t getMSecs() {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return (uint32_t) (tv.tv_sec * 1000 + tv.tv_usec / 1000 ); // microseconds to milliseconds
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+BackgroundPlugin::BackgroundPlugin(NPP inst) : SubPlugin(inst) {
+
+    // initialize the drawing surface
+    m_surfaceReady = false;
+    m_surface = gSurfaceI.newRasterSurface(inst, kRGB_565_ANPBitmapFormat, false);
+    if(!m_surface)
+        gLogI.log(inst, kError_ANPLogType, "----%p Unable to create RGBA surface", inst);
+
+    //initialize bitmap transparency variables
+    mFinishedStageOne   = false;
+    mFinishedStageTwo   = false;
+    mFinishedStageThree = false;
+
+    // test basic plugin functionality
+    test_logging(); // android logging
+    test_timers();  // plugin timers
+    test_bitmaps(); // android bitmaps
+    test_domAccess();
+    test_javascript();
+}
+
+BackgroundPlugin::~BackgroundPlugin() {
+    gSurfaceI.deleteSurface(m_surface);
+}
+
+bool BackgroundPlugin::supportsDrawingModel(ANPDrawingModel model) {
+    return (model == kSurface_ANPDrawingModel);
+}
+
+void BackgroundPlugin::drawPlugin(int surfaceWidth, int surfaceHeight) {
+
+    // get the plugin's dimensions according to the DOM
+    PluginObject *obj = (PluginObject*) inst()->pdata;
+    const int W = obj->window->width;
+    const int H = obj->window->height;
+
+    // compute the current zoom level
+    const float zoomFactorW = static_cast<float>(surfaceWidth) / W;
+    const float zoomFactorH = static_cast<float>(surfaceHeight) / H;
+
+    // check to make sure the zoom level is uniform
+    if (zoomFactorW + .01 < zoomFactorH && zoomFactorW - .01 > zoomFactorH)
+        gLogI.log(inst(), kError_ANPLogType, " ------ %p zoom is out of sync (%f,%f)",
+                  inst(), zoomFactorW, zoomFactorH);
+
+    // scale the variables based on the zoom level
+    const int fontSize = (int)(zoomFactorW * 16);
+    const int leftMargin = (int)(zoomFactorW * 10);
+
+    // lock the surface
+    ANPBitmap bitmap;
+    if (!m_surfaceReady || !gSurfaceI.lock(m_surface, &bitmap, NULL)) {
+        gLogI.log(inst(), kError_ANPLogType, " ------ %p unable to lock the plugin", inst());
+        return;
+    }
+
+    // create a canvas
+    ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
+    gCanvasI.drawColor(canvas, 0xFFFFFFFF);
+
+    ANPPaint* paint = gPaintI.newPaint();
+    gPaintI.setFlags(paint, gPaintI.getFlags(paint) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(paint, 0xFFFF0000);
+    gPaintI.setTextSize(paint, fontSize);
+
+    ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
+    gPaintI.setTypeface(paint, tf);
+    gTypefaceI.unref(tf);
+
+    ANPFontMetrics fm;
+    gPaintI.getFontMetrics(paint, &fm);
+
+    gPaintI.setColor(paint, 0xFF0000FF);
+    const char c[] = "This is a background plugin.";
+    gCanvasI.drawText(canvas, c, sizeof(c)-1, leftMargin, -fm.fTop, paint);
+
+    // clean up variables and unlock the surface
+    gPaintI.deletePaint(paint);
+    gCanvasI.deleteCanvas(canvas);
+    gSurfaceI.unlock(m_surface);
+}
+
+int16 BackgroundPlugin::handleEvent(const ANPEvent* evt) {
+    switch (evt->eventType) {
+        case kDraw_ANPEventType:
+            gLogI.log(inst(), kError_ANPLogType, " ------ %p the plugin did not request draw events", inst());
+            break;
+        case kSurface_ANPEventType:
+                    switch (evt->data.surface.action) {
+                        case kCreated_ANPSurfaceAction:
+                            m_surfaceReady = true;
+                            return 1;
+                        case kDestroyed_ANPSurfaceAction:
+                            m_surfaceReady = false;
+                            return 1;
+                        case kChanged_ANPSurfaceAction:
+                            drawPlugin(evt->data.surface.data.changed.width,
+                                       evt->data.surface.data.changed.height);
+                            return 1;
+                    }
+                    break;
+        case kLifecycle_ANPEventType:
+            if (evt->data.lifecycle.action == kOnLoad_ANPLifecycleAction) {
+                gLogI.log(inst(), kDebug_ANPLogType, " ------ %p the plugin received an onLoad event", inst());
+                return 1;
+            }
+            break;
+        case kTouch_ANPEventType:
+            gLogI.log(inst(), kError_ANPLogType, " ------ %p the plugin did not request touch events", inst());
+            break;
+        case kKey_ANPEventType:
+            gLogI.log(inst(), kError_ANPLogType, " ------ %p the plugin did not request key events", inst());
+            break;
+        default:
+            break;
+    }
+    return 0;   // unknown or unhandled event
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LOGGING TESTS
+///////////////////////////////////////////////////////////////////////////////
+
+
+void BackgroundPlugin::test_logging() {
+    NPP instance = this->inst();
+
+    //LOG_ERROR(instance, " ------ %p Testing Log Error", instance);
+    gLogI.log(instance, kError_ANPLogType, " ------ %p Testing Log Error", instance);
+    gLogI.log(instance, kWarning_ANPLogType, " ------ %p Testing Log Warning", instance);
+    gLogI.log(instance, kDebug_ANPLogType, " ------ %p Testing Log Debug", instance);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TIMER TESTS
+///////////////////////////////////////////////////////////////////////////////
+
+#define TIMER_INTERVAL     50
+static void timer_oneshot(NPP instance, uint32 timerID);
+static void timer_repeat(NPP instance, uint32 timerID);
+static void timer_neverfires(NPP instance, uint32 timerID);
+static void timer_latency(NPP instance, uint32 timerID);
+
+void BackgroundPlugin::test_timers() {
+    NPP instance = this->inst();
+
+    //Setup the testing counters
+    mTimerRepeatCount = 5;
+    mTimerLatencyCount = 5;
+
+    // test for bogus timerID
+    browser->unscheduletimer(instance, 999999);
+    // test one-shot
+    browser->scheduletimer(instance, 100, false, timer_oneshot);
+    // test repeat
+    browser->scheduletimer(instance, 50, true, timer_repeat);
+    // test timer latency
+    browser->scheduletimer(instance, TIMER_INTERVAL, true, timer_latency);
+    mStartTime = mPrevTime = getMSecs();
+    // test unschedule immediately
+    uint32 id = browser->scheduletimer(instance, 100, false, timer_neverfires);
+    browser->unscheduletimer(instance, id);
+    // test double unschedule (should be no-op)
+    browser->unscheduletimer(instance, id);
+
+}
+
+static void timer_oneshot(NPP instance, uint32 timerID) {
+    gLogI.log(instance, kDebug_ANPLogType, "-------- oneshot timer\n");
+}
+
+static void timer_repeat(NPP instance, uint32 timerID) {
+    BackgroundPlugin *obj = ((BackgroundPlugin*) ((PluginObject*) instance->pdata)->activePlugin);
+
+    gLogI.log(instance, kDebug_ANPLogType, "-------- repeat timer %d\n",
+              obj->mTimerRepeatCount);
+    if (--obj->mTimerRepeatCount == 0) {
+        browser->unscheduletimer(instance, timerID);
+    }
+}
+
+static void timer_neverfires(NPP instance, uint32 timerID) {
+    gLogI.log(instance, kError_ANPLogType, "-------- timer_neverfires!!!\n");
+}
+
+static void timer_latency(NPP instance, uint32 timerID) {
+    BackgroundPlugin *obj = ((BackgroundPlugin*) ((PluginObject*) instance->pdata)->activePlugin);
+
+    obj->mTimerLatencyCurrentCount += 1;
+
+    uint32_t now = getMSecs();
+    uint32_t interval = now - obj->mPrevTime;
+    uint32_t dur = now - obj->mStartTime;
+    uint32_t expectedDur = obj->mTimerLatencyCurrentCount * TIMER_INTERVAL;
+    int32_t drift = dur - expectedDur;
+    int32_t avgDrift = drift / obj->mTimerLatencyCurrentCount;
+
+    obj->mPrevTime = now;
+
+    gLogI.log(instance, kDebug_ANPLogType,
+              "-------- latency test: [%3d] interval %d expected %d, total %d expected %d, drift %d avg %d\n",
+              obj->mTimerLatencyCurrentCount, interval, TIMER_INTERVAL, dur,
+              expectedDur, drift, avgDrift);
+
+    if (--obj->mTimerLatencyCount == 0) {
+        browser->unscheduletimer(instance, timerID);
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// BITMAP TESTS
+///////////////////////////////////////////////////////////////////////////////
+
+static void test_formats(NPP instance);
+
+void BackgroundPlugin::test_bitmaps() {
+    test_formats(this->inst());
+}
+
+static void test_formats(NPP instance) {
+
+    // TODO pull names from enum in npapi instead of hardcoding them
+    static const struct {
+        ANPBitmapFormat fFormat;
+        const char*     fName;
+    } gRecs[] = {
+        { kUnknown_ANPBitmapFormat,   "unknown" },
+        { kRGBA_8888_ANPBitmapFormat, "8888" },
+        { kRGB_565_ANPBitmapFormat,   "565" },
+    };
+
+    ANPPixelPacking packing;
+    for (size_t i = 0; i < ARRAY_COUNT(gRecs); i++) {
+        if (gBitmapI.getPixelPacking(gRecs[i].fFormat, &packing)) {
+            gLogI.log(instance, kDebug_ANPLogType,
+                      "pixel format [%d] %s has packing ARGB [%d %d] [%d %d] [%d %d] [%d %d]\n",
+                      gRecs[i].fFormat, gRecs[i].fName,
+                      packing.AShift, packing.ABits,
+                      packing.RShift, packing.RBits,
+                      packing.GShift, packing.GBits,
+                      packing.BShift, packing.BBits);
+        } else {
+            gLogI.log(instance, kDebug_ANPLogType,
+                      "pixel format [%d] %s has no packing\n",
+                      gRecs[i].fFormat, gRecs[i].fName);
+        }
+    }
+}
+
+void BackgroundPlugin::test_bitmap_transparency(const ANPEvent* evt) {
+    NPP instance = this->inst();
+
+    // check default & set transparent
+    if (!mFinishedStageOne) {
+
+        gLogI.log(instance, kDebug_ANPLogType, "BEGIN: testing bitmap transparency");
+
+        //check to make sure it is not transparent
+        if (evt->data.draw.data.bitmap.format == kRGBA_8888_ANPBitmapFormat) {
+            gLogI.log(instance, kError_ANPLogType, "bitmap default format is transparent");
+        }
+
+        //make it transparent (any non-null value will set it to true)
+        bool value = true;
+        NPError err = browser->setvalue(instance, NPPVpluginTransparentBool, &value);
+        if (err != NPERR_NO_ERROR) {
+            gLogI.log(instance, kError_ANPLogType, "Error setting transparency.");
+        }
+
+        mFinishedStageOne = true;
+        browser->invalidaterect(instance, NULL);
+    }
+    // check transparent & set opaque
+    else if (!mFinishedStageTwo) {
+
+        //check to make sure it is transparent
+        if (evt->data.draw.data.bitmap.format != kRGBA_8888_ANPBitmapFormat) {
+            gLogI.log(instance, kError_ANPLogType, "bitmap did not change to transparent format");
+        }
+
+        //make it opaque
+        NPError err = browser->setvalue(instance, NPPVpluginTransparentBool, NULL);
+        if (err != NPERR_NO_ERROR) {
+            gLogI.log(instance, kError_ANPLogType, "Error setting transparency.");
+        }
+
+        mFinishedStageTwo = true;
+    }
+    // check opaque
+    else if (!mFinishedStageThree) {
+
+        //check to make sure it is not transparent
+        if (evt->data.draw.data.bitmap.format == kRGBA_8888_ANPBitmapFormat) {
+            gLogI.log(instance, kError_ANPLogType, "bitmap default format is transparent");
+        }
+
+        gLogI.log(instance, kDebug_ANPLogType, "END: testing bitmap transparency");
+
+        mFinishedStageThree = true;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DOM TESTS
+///////////////////////////////////////////////////////////////////////////////
+
+void BackgroundPlugin::test_domAccess() {
+    NPP instance = this->inst();
+
+    gLogI.log(instance, kDebug_ANPLogType, " ------ %p Testing DOM Access", instance);
+
+    // Get the plugin's DOM object
+    NPObject* windowObject = NULL;
+    browser->getvalue(instance, NPNVWindowNPObject, &windowObject);
+
+    if (!windowObject)
+        gLogI.log(instance, kError_ANPLogType, " ------ %p Unable to retrieve DOM Window", instance);
+
+    // Retrieve a property from the plugin's DOM object
+    NPIdentifier topIdentifier = browser->getstringidentifier("top");
+    NPVariant topObjectVariant;
+    browser->getproperty(instance, windowObject, topIdentifier, &topObjectVariant);
+
+    if (topObjectVariant.type != NPVariantType_Object)
+        gLogI.log(instance, kError_ANPLogType, " ------ %p Invalid Variant type for DOM Property: %d,%d", instance, topObjectVariant.type, NPVariantType_Object);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// JAVASCRIPT TESTS
+///////////////////////////////////////////////////////////////////////////////
+
+
+void BackgroundPlugin::test_javascript() {
+    NPP instance = this->inst();
+
+    gLogI.log(instance, kDebug_ANPLogType, " ------ %p Testing JavaScript Access", instance);
+
+    // Get the plugin's DOM object
+    NPObject* windowObject = NULL;
+    browser->getvalue(instance, NPNVWindowNPObject, &windowObject);
+
+    if (!windowObject)
+        gLogI.log(instance, kError_ANPLogType, " ------ %p Unable to retrieve DOM Window", instance);
+
+    // create a string (JS code) that is stored in memory allocated by the browser
+    const char* jsString = "1200 + 34";
+    void* stringMem = browser->memalloc(strlen(jsString));
+    memcpy(stringMem, jsString, strlen(jsString));
+
+    // execute the javascript in the plugin's DOM object
+    NPString script = { (char*)stringMem, strlen(jsString) };
+    NPVariant scriptVariant;
+    if (!browser->evaluate(instance, windowObject, &script, &scriptVariant))
+        gLogI.log(instance, kError_ANPLogType, " ------ %p Unable to eval the JS.", instance);
+
+    if (scriptVariant.type == NPVariantType_Int32) {
+        if (scriptVariant.value.intValue != 1234)
+            gLogI.log(instance, kError_ANPLogType, " ------ %p Invalid Value for JS Return: %d,1234", instance, scriptVariant.value.intValue);
+    } else {
+        gLogI.log(instance, kError_ANPLogType, " ------ %p Invalid Variant type for JS Return: %d,%d", instance, scriptVariant.type, NPVariantType_Int32);
+    }
+
+    // free the memory allocated within the browser
+    browser->memfree(stringMem);
+}
diff --git a/samples/BrowserPlugin/jni/background/BackgroundPlugin.h b/samples/BrowserPlugin/jni/background/BackgroundPlugin.h
new file mode 100644
index 0000000..ed428b5
--- /dev/null
+++ b/samples/BrowserPlugin/jni/background/BackgroundPlugin.h
@@ -0,0 +1,65 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "PluginObject.h"
+
+#ifndef backgroundPlugin__DEFINED
+#define backgroundPlugin__DEFINED
+
+class BackgroundPlugin : public SubPlugin {
+public:
+    BackgroundPlugin(NPP inst);
+    virtual ~BackgroundPlugin();
+    virtual bool supportsDrawingModel(ANPDrawingModel);
+    virtual int16 handleEvent(const ANPEvent* evt);
+
+    // Timer Testing Variables
+    uint32_t mStartTime;
+    uint32_t mPrevTime;
+    int      mTimerRepeatCount;
+    int      mTimerLatencyCount;
+    int      mTimerLatencyCurrentCount;
+
+    // Bitmap Transparency Variables
+    bool mFinishedStageOne;   // check default & set transparent
+    bool mFinishedStageTwo;   // check transparent & set opaque
+    bool mFinishedStageThree; // check opaque
+
+private:
+    void drawPlugin(int surfaceWidth, int surfaceHeight);
+
+    bool        m_surfaceReady;
+    ANPSurface* m_surface;
+
+    void test_logging();
+    void test_timers();
+    void test_bitmaps();
+    void test_bitmap_transparency(const ANPEvent* evt);
+    void test_domAccess();
+    void test_javascript();
+
+};
+
+#endif // backgroundPlugin__DEFINED
diff --git a/samples/BrowserPlugin/jni/form/FormPlugin.cpp b/samples/BrowserPlugin/jni/form/FormPlugin.cpp
new file mode 100644
index 0000000..a92d447
--- /dev/null
+++ b/samples/BrowserPlugin/jni/form/FormPlugin.cpp
@@ -0,0 +1,381 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "FormPlugin.h"
+
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+
+extern NPNetscapeFuncs*         browser;
+extern ANPLogInterfaceV0        gLogI;
+extern ANPCanvasInterfaceV0     gCanvasI;
+extern ANPPaintInterfaceV0      gPaintI;
+extern ANPTypefaceInterfaceV0   gTypefaceI;
+extern ANPWindowInterfaceV0     gWindowI;
+
+
+static void inval(NPP instance) {
+    browser->invalidaterect(instance, NULL);
+}
+
+static uint16 rnd16(float x, int inset) {
+    int ix = (int)roundf(x) + inset;
+    if (ix < 0) {
+        ix = 0;
+    }
+    return static_cast<uint16>(ix);
+}
+
+static void inval(NPP instance, const ANPRectF& r, bool doAA) {
+    const int inset = doAA ? -1 : 0;
+
+    PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata);
+    NPRect inval;
+    inval.left = rnd16(r.left, inset);
+    inval.top = rnd16(r.top, inset);
+    inval.right = rnd16(r.right, -inset);
+    inval.bottom = rnd16(r.bottom, -inset);
+    browser->invalidaterect(instance, &inval);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FormPlugin::FormPlugin(NPP inst) : SubPlugin(inst) {
+
+    m_hasFocus = false;
+    m_activeInput = NULL;
+
+    memset(&m_usernameInput, 0, sizeof(m_usernameInput));
+    memset(&m_passwordInput, 0, sizeof(m_passwordInput));
+
+    m_usernameInput.text[0] = '\0';
+    m_usernameInput.charPtr = 0;
+
+    m_passwordInput.text[0] = '\0';
+    m_passwordInput.charPtr = 0;
+
+    m_paintInput = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintInput, gPaintI.getFlags(m_paintInput) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintInput, 0xFFFFFFFF);
+
+    m_paintActive = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintActive, gPaintI.getFlags(m_paintActive) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintActive, 0xFFFFFF00);
+
+    m_paintText = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintText, gPaintI.getFlags(m_paintText) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintText, 0xFF000000);
+    gPaintI.setTextSize(m_paintText, 18);
+
+    ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
+    gPaintI.setTypeface(m_paintText, tf);
+    gTypefaceI.unref(tf);
+
+    //register for key and visibleRect events
+    ANPEventFlags flags = kKey_ANPEventFlag;
+    NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
+    if (err != NPERR_NO_ERROR) {
+        gLogI.log(inst, kError_ANPLogType, "Error selecting input events.");
+    }
+}
+
+FormPlugin::~FormPlugin() {
+    gPaintI.deletePaint(m_paintInput);
+    gPaintI.deletePaint(m_paintActive);
+    gPaintI.deletePaint(m_paintText);
+}
+
+bool FormPlugin::supportsDrawingModel(ANPDrawingModel model) {
+    return (model == kBitmap_ANPDrawingModel);
+}
+
+void FormPlugin::drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip) {
+    ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
+
+    ANPRectF clipR;
+    clipR.left = clip.left;
+    clipR.top = clip.top;
+    clipR.right = clip.right;
+    clipR.bottom = clip.bottom;
+    gCanvasI.clipRect(canvas, &clipR);
+
+    draw(canvas);
+    gCanvasI.deleteCanvas(canvas);
+}
+
+void FormPlugin::draw(ANPCanvas* canvas) {
+    NPP instance = this->inst();
+    PluginObject *obj = (PluginObject*) instance->pdata;
+
+    const float inputWidth = 60;
+    const float inputHeight = 30;
+    const int W = obj->window->width;
+    const int H = obj->window->height;
+
+    // color the plugin canvas
+    gCanvasI.drawColor(canvas, (m_hasFocus) ? 0xFFCDCDCD : 0xFF545454);
+
+    // draw the username box (5 px from the top edge)
+    m_usernameInput.rect.left = 5;
+    m_usernameInput.rect.top = 5;
+    m_usernameInput.rect.right = W - 5;
+    m_usernameInput.rect.bottom = m_usernameInput.rect.top + inputHeight;
+    gCanvasI.drawRect(canvas, &m_usernameInput.rect, getPaint(&m_usernameInput));
+    drawText(canvas, m_usernameInput);
+
+    // draw the password box (5 px from the bottom edge)
+    m_passwordInput.rect.left = 5;
+    m_passwordInput.rect.top = H - (inputHeight + 5);
+    m_passwordInput.rect.right = W - 5;
+    m_passwordInput.rect.bottom = m_passwordInput.rect.top + inputHeight;
+    gCanvasI.drawRect(canvas, &m_passwordInput.rect, getPaint(&m_passwordInput));
+    drawPassword(canvas, m_passwordInput);
+
+    //invalidate the canvas
+    //inval(instance);
+}
+
+ANPPaint* FormPlugin::getPaint(TextInput* input) {
+    return (input == m_activeInput) ? m_paintActive : m_paintInput;
+}
+
+void FormPlugin::drawText(ANPCanvas* canvas, TextInput textInput) {
+
+    // get font metrics
+    ANPFontMetrics fontMetrics;
+    gPaintI.getFontMetrics(m_paintText, &fontMetrics);
+
+    gCanvasI.drawText(canvas, textInput.text, textInput.charPtr,
+                      textInput.rect.left + 5,
+                      textInput.rect.bottom - fontMetrics.fBottom, m_paintText);
+}
+
+void FormPlugin::drawPassword(ANPCanvas* canvas, TextInput passwordInput) {
+
+    // get font metrics
+    ANPFontMetrics fontMetrics;
+    gPaintI.getFontMetrics(m_paintText, &fontMetrics);
+
+    // comput the circle dimensions and initial location
+    float initialX = passwordInput.rect.left + 5;
+    float ovalBottom = passwordInput.rect.bottom - 2;
+    float ovalTop = ovalBottom - (fontMetrics.fBottom - fontMetrics.fTop);
+    float ovalWidth = ovalBottom - ovalTop;
+    float ovalSpacing = 3;
+
+    // draw circles instead of the actual text
+    for (uint32_t x = 0; x < passwordInput.charPtr; x++) {
+        ANPRectF oval;
+        oval.left = initialX + ((ovalWidth + ovalSpacing) * (float) x);
+        oval.right = oval.left + ovalWidth;
+        oval.top = ovalTop;
+        oval.bottom = ovalBottom;
+        gCanvasI.drawOval(canvas, &oval, m_paintText);
+    }
+}
+
+int16 FormPlugin::handleEvent(const ANPEvent* evt) {
+    NPP instance = this->inst();
+
+    switch (evt->eventType) {
+        case kDraw_ANPEventType:
+            switch (evt->data.draw.model) {
+                case kBitmap_ANPDrawingModel:
+                    drawPlugin(evt->data.draw.data.bitmap, evt->data.draw.clip);
+                    return 1;
+                default:
+                    break;   // unknown drawing model
+            }
+            break;
+
+        case kLifecycle_ANPEventType:
+            if (evt->data.lifecycle.action == kLoseFocus_ANPLifecycleAction) {
+                gLogI.log(instance, kDebug_ANPLogType, "----%p Loosing Focus", instance);
+
+                if (m_activeInput) {
+                    // hide the keyboard
+                    gWindowI.showKeyboard(instance, false);
+
+                    //reset the activeInput
+                    m_activeInput = NULL;
+                }
+
+                m_hasFocus = false;
+                inval(instance);
+                return 1;
+            }
+            else if (evt->data.lifecycle.action == kGainFocus_ANPLifecycleAction) {
+                gLogI.log(instance, kDebug_ANPLogType, "----%p Gaining Focus", instance);
+                m_hasFocus = true;
+                inval(instance);
+                return 1;
+            }
+            break;
+
+        case kMouse_ANPEventType: {
+
+            int x = evt->data.mouse.x;
+            int y = evt->data.mouse.y;
+            if (kDown_ANPMouseAction == evt->data.mouse.action) {
+
+                TextInput* currentInput = validTap(x,y);
+
+                if (currentInput)
+                    gWindowI.showKeyboard(instance, true);
+                else if (m_activeInput)
+                    gWindowI.showKeyboard(instance, false);
+
+                if (currentInput != m_activeInput)
+                    switchActiveInput(currentInput);
+
+                return 1;
+            }
+            break;
+        }
+
+        case kKey_ANPEventType:
+            if (evt->data.key.action == kDown_ANPKeyAction) {
+
+                //handle navigation keys
+                if (evt->data.key.nativeCode >= kDpadUp_ANPKeyCode
+                        && evt->data.key.nativeCode <= kDpadCenter_ANPKeyCode) {
+                    return handleNavigation(evt->data.key.nativeCode) ? 1 : 0;
+                }
+
+                if (m_activeInput) {
+                    handleTextInput(m_activeInput, evt->data.key.nativeCode,
+                                    evt->data.key.unichar);
+                    inval(instance, m_activeInput->rect, true);
+                }
+            }
+            return 1;
+
+        default:
+            break;
+    }
+    return 0;   // unknown or unhandled event
+}
+
+void FormPlugin::switchActiveInput(TextInput* newInput) {
+    NPP instance = this->inst();
+
+    if (m_activeInput) {
+        inval(instance, m_activeInput->rect, true); // inval the old
+        gWindowI.clearVisibleRects(instance);
+    }
+
+    m_activeInput = newInput; // set the new active input
+
+    if (m_activeInput) {
+        inval(instance, m_activeInput->rect, true); // inval the new
+        scrollIntoView(m_activeInput);
+    }
+}
+
+bool FormPlugin::handleNavigation(ANPKeyCode keyCode) {
+    NPP instance = this->inst();
+
+    gLogI.log(instance, kDebug_ANPLogType, "----%p Recvd Nav Key %d", instance, keyCode);
+
+    if (!m_activeInput) {
+        gWindowI.showKeyboard(instance, true);
+        switchActiveInput(&m_usernameInput);
+    }
+    else if (m_activeInput == &m_usernameInput) {
+        if (keyCode == kDpadDown_ANPKeyCode) {
+            switchActiveInput(&m_passwordInput);
+        }
+        else if (keyCode == kDpadCenter_ANPKeyCode)
+            gWindowI.showKeyboard(instance, false);
+        else if (keyCode == kDpadUp_ANPKeyCode)
+            return false;
+    }
+    else if (m_activeInput == &m_passwordInput) {
+        if (keyCode == kDpadUp_ANPKeyCode) {
+            switchActiveInput(&m_usernameInput);
+        }
+        else if (keyCode == kDpadCenter_ANPKeyCode)
+            gWindowI.showKeyboard(instance, false);
+        else if (keyCode == kDpadDown_ANPKeyCode)
+            return false;
+    }
+
+    return true;
+}
+
+void FormPlugin::handleTextInput(TextInput* input, ANPKeyCode keyCode, int32_t unichar) {
+    NPP instance = this->inst();
+
+    //make sure the input field is in view
+    scrollIntoView(input);
+
+    //handle the delete operation
+    if (keyCode == kDel_ANPKeyCode) {
+        if (input->charPtr > 0) {
+            input->charPtr--;
+        }
+        return;
+    }
+
+    //check to see that the input is not full
+    if (input->charPtr >= (sizeof(input->text) - 1))
+        return;
+
+    //add the character
+    input->text[input->charPtr] = static_cast<char>(unichar);
+    input->charPtr++;
+
+    gLogI.log(instance, kDebug_ANPLogType, "----%p Text:  %c", instance, unichar);
+}
+
+void FormPlugin::scrollIntoView(TextInput* input) {
+    NPP instance = this->inst();
+    PluginObject *obj = (PluginObject*) instance->pdata;
+    NPWindow *window = obj->window;
+
+    // find the textInput's global rect coordinates
+    ANPRectI visibleRects[1];
+    visibleRects[0].left = input->rect.left;
+    visibleRects[0].top = input->rect.top;
+    visibleRects[0].right = input->rect.right;
+    visibleRects[0].bottom = input->rect.bottom;
+
+    gWindowI.setVisibleRects(instance, visibleRects, 1);
+}
+
+TextInput* FormPlugin::validTap(int x, int y) {
+
+    if (x > m_usernameInput.rect.left && x < m_usernameInput.rect.right &&
+        y > m_usernameInput.rect.top && y < m_usernameInput.rect.bottom)
+        return &m_usernameInput;
+    else if (x >m_passwordInput.rect.left && x < m_passwordInput.rect.right &&
+             y > m_passwordInput.rect.top && y < m_passwordInput.rect.bottom)
+        return &m_passwordInput;
+    else
+        return NULL;
+}
diff --git a/samples/BrowserPlugin/jni/form/FormPlugin.h b/samples/BrowserPlugin/jni/form/FormPlugin.h
new file mode 100644
index 0000000..041ffb8
--- /dev/null
+++ b/samples/BrowserPlugin/jni/form/FormPlugin.h
@@ -0,0 +1,72 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "PluginObject.h"
+
+#ifndef formPlugin__DEFINED
+#define formPlugin__DEFINED
+
+struct TextInput {
+    ANPRectF    rect;
+    char        text[30];
+    uint32_t    charPtr;
+};
+
+class FormPlugin : public SubPlugin {
+public:
+    FormPlugin(NPP inst);
+    virtual ~FormPlugin();
+    virtual bool supportsDrawingModel(ANPDrawingModel);
+    virtual int16 handleEvent(const ANPEvent* evt);
+private:
+    void draw(ANPCanvas*);
+    void drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip);
+
+    bool        m_hasFocus;
+
+    TextInput*  m_activeInput;
+    TextInput   m_usernameInput;
+    TextInput   m_passwordInput;
+
+    ANPPaint*   m_paintInput;
+    ANPPaint*   m_paintActive;
+    ANPPaint*   m_paintText;
+
+    ANPRectI    m_visibleRect;
+
+    void drawText(ANPCanvas*, TextInput);
+    void drawPassword(ANPCanvas*, TextInput);
+
+    bool handleNavigation(ANPKeyCode keyCode);
+    void handleTextInput(TextInput* input, ANPKeyCode keyCode, int32_t unichar);
+    void scrollIntoView(TextInput* input);
+    void switchActiveInput(TextInput* input);
+
+    ANPPaint* getPaint(TextInput*);
+    TextInput* validTap(int x, int y);
+
+};
+
+#endif // formPlugin__DEFINED
diff --git a/samples/BrowserPlugin/jni/main.cpp b/samples/BrowserPlugin/jni/main.cpp
new file mode 100644
index 0000000..b5aea95
--- /dev/null
+++ b/samples/BrowserPlugin/jni/main.cpp
@@ -0,0 +1,404 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "main.h"
+#include "PluginObject.h"
+#include "AnimationPlugin.h"
+#include "AudioPlugin.h"
+#include "BackgroundPlugin.h"
+#include "FormPlugin.h"
+#include "PaintPlugin.h"
+#include "android_npapi.h"
+
+NPNetscapeFuncs* browser;
+#define EXPORT __attribute__((visibility("default")))
+
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
+        char* argn[], char* argv[], NPSavedData* saved);
+NPError NPP_Destroy(NPP instance, NPSavedData** save);
+NPError NPP_SetWindow(NPP instance, NPWindow* window);
+NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream,
+        NPBool seekable, uint16* stype);
+NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason);
+int32   NPP_WriteReady(NPP instance, NPStream* stream);
+int32   NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len,
+        void* buffer);
+void    NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname);
+void    NPP_Print(NPP instance, NPPrint* platformPrint);
+int16   NPP_HandleEvent(NPP instance, void* event);
+void    NPP_URLNotify(NPP instance, const char* URL, NPReason reason,
+        void* notifyData);
+NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value);
+NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value);
+
+extern "C" {
+EXPORT NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env, void *application_context);
+EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value);
+EXPORT const char* NP_GetMIMEDescription(void);
+EXPORT void NP_Shutdown(void);
+};
+
+ANPAudioTrackInterfaceV0    gSoundI;
+ANPBitmapInterfaceV0        gBitmapI;
+ANPCanvasInterfaceV0        gCanvasI;
+ANPLogInterfaceV0           gLogI;
+ANPPaintInterfaceV0         gPaintI;
+ANPPathInterfaceV0          gPathI;
+ANPSurfaceInterfaceV0       gSurfaceI;
+ANPSystemInterfaceV0        gSystemI;
+ANPTypefaceInterfaceV0      gTypefaceI;
+ANPWindowInterfaceV0        gWindowI;
+
+#define ARRAY_COUNT(array)      (sizeof(array) / sizeof(array[0]))
+#define DEBUG_PLUGIN_EVENTS     0
+
+NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env, void *application_context)
+{
+    // Make sure we have a function table equal or larger than we are built against.
+    if (browserFuncs->size < sizeof(NPNetscapeFuncs)) {
+        return NPERR_GENERIC_ERROR;
+    }
+
+    // Copy the function table (structure)
+    browser = (NPNetscapeFuncs*) malloc(sizeof(NPNetscapeFuncs));
+    memcpy(browser, browserFuncs, sizeof(NPNetscapeFuncs));
+
+    // Build the plugin function table
+    pluginFuncs->version = 11;
+    pluginFuncs->size = sizeof(pluginFuncs);
+    pluginFuncs->newp = NPP_New;
+    pluginFuncs->destroy = NPP_Destroy;
+    pluginFuncs->setwindow = NPP_SetWindow;
+    pluginFuncs->newstream = NPP_NewStream;
+    pluginFuncs->destroystream = NPP_DestroyStream;
+    pluginFuncs->asfile = NPP_StreamAsFile;
+    pluginFuncs->writeready = NPP_WriteReady;
+    pluginFuncs->write = (NPP_WriteProcPtr)NPP_Write;
+    pluginFuncs->print = NPP_Print;
+    pluginFuncs->event = NPP_HandleEvent;
+    pluginFuncs->urlnotify = NPP_URLNotify;
+    pluginFuncs->getvalue = NPP_GetValue;
+    pluginFuncs->setvalue = NPP_SetValue;
+
+    static const struct {
+        NPNVariable     v;
+        uint32_t        size;
+        ANPInterface*   i;
+    } gPairs[] = {
+        { kAudioTrackInterfaceV0_ANPGetValue,   sizeof(gSoundI),    &gSoundI },
+        { kBitmapInterfaceV0_ANPGetValue,       sizeof(gBitmapI),   &gBitmapI },
+        { kCanvasInterfaceV0_ANPGetValue,       sizeof(gCanvasI),   &gCanvasI },
+        { kLogInterfaceV0_ANPGetValue,          sizeof(gLogI),      &gLogI },
+        { kPaintInterfaceV0_ANPGetValue,        sizeof(gPaintI),    &gPaintI },
+        { kPathInterfaceV0_ANPGetValue,         sizeof(gPathI),     &gPathI },
+        { kSurfaceInterfaceV0_ANPGetValue,      sizeof(gSurfaceI),  &gSurfaceI },
+        { kSystemInterfaceV0_ANPGetValue,       sizeof(gSystemI),   &gSystemI },
+        { kTypefaceInterfaceV0_ANPGetValue,     sizeof(gTypefaceI), &gTypefaceI },
+        { kWindowInterfaceV0_ANPGetValue,       sizeof(gWindowI),   &gWindowI },
+    };
+    for (size_t i = 0; i < ARRAY_COUNT(gPairs); i++) {
+        gPairs[i].i->inSize = gPairs[i].size;
+        NPError err = browser->getvalue(NULL, gPairs[i].v, gPairs[i].i);
+        if (err) {
+            return err;
+        }
+    }
+
+    return NPERR_NO_ERROR;
+}
+
+void NP_Shutdown(void)
+{
+
+}
+
+const char *NP_GetMIMEDescription(void)
+{
+    return "application/x-testbrowserplugin:tst:Test plugin mimetype is application/x-testbrowserplugin";
+}
+
+NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
+                char* argn[], char* argv[], NPSavedData* saved)
+{
+
+    /* BEGIN: STANDARD PLUGIN FRAMEWORK */
+    PluginObject *obj = NULL;
+
+    // Scripting functions appeared in NPAPI version 14
+    if (browser->version >= 14) {
+    instance->pdata = browser->createobject (instance, getPluginClass());
+    obj = static_cast<PluginObject*>(instance->pdata);
+    bzero(obj, sizeof(*obj));
+    }
+    /* END: STANDARD PLUGIN FRAMEWORK */
+
+    // select the drawing model based on user input
+    ANPDrawingModel model = kBitmap_ANPDrawingModel;
+
+    for (int i = 0; i < argc; i++) {
+        if (!strcmp(argn[i], "DrawingModel")) {
+            if (!strcmp(argv[i], "Bitmap")) {
+                model = kBitmap_ANPDrawingModel;
+            }
+            else if (!strcmp(argv[i], "Surface")) {
+               model = kSurface_ANPDrawingModel;
+            }
+            gLogI.log(instance, kDebug_ANPLogType, "------ %p DrawingModel is %d", instance, model);
+            break;
+        }
+    }
+
+    // notify the plugin API of the drawing model we wish to use. This must be
+    // done prior to creating certain subPlugin objects (e.g. surfaceViews)
+    NPError err = browser->setvalue(instance, kRequestDrawingModel_ANPSetValue,
+                            reinterpret_cast<void*>(model));
+    if (err) {
+        gLogI.log(instance, kError_ANPLogType, "request model %d err %d", model, err);
+        return err;
+    }
+
+    const char* path = gSystemI.getApplicationDataDirectory();
+    if (path) {
+        gLogI.log(instance, kDebug_ANPLogType, "Application data dir is %s", path);
+    } else {
+        gLogI.log(instance, kError_ANPLogType, "Can't find Application data dir");
+    }
+
+    // select the pluginType
+    for (int i = 0; i < argc; i++) {
+        if (!strcmp(argn[i], "PluginType")) {
+            if (!strcmp(argv[i], "Animation")) {
+                obj->pluginType = kAnimation_PluginType;
+                obj->activePlugin = new BallAnimation(instance);
+            }
+            else if (!strcmp(argv[i], "Audio")) {
+                obj->pluginType = kAudio_PluginType;
+                obj->activePlugin = new AudioPlugin(instance);
+            }
+            else if (!strcmp(argv[i], "Background")) {
+                obj->pluginType = kBackground_PluginType;
+                obj->activePlugin = new BackgroundPlugin(instance);
+            }
+            else if (!strcmp(argv[i], "Form")) {
+                obj->pluginType = kForm_PluginType;
+                obj->activePlugin = new FormPlugin(instance);
+            }
+            else if (!strcmp(argv[i], "Paint")) {
+                obj->pluginType = kPaint_PluginType;
+                obj->activePlugin = new PaintPlugin(instance);
+            }
+            gLogI.log(instance, kDebug_ANPLogType, "------ %p PluginType is %d", instance, obj->pluginType);
+            break;
+        }
+    }
+
+    // if no pluginType is specified then default to Animation
+    if (!obj->pluginType) {
+        gLogI.log(instance, kError_ANPLogType, "------ %p No PluginType attribute was found", instance);
+        obj->pluginType = kAnimation_PluginType;
+        obj->activePlugin = new BallAnimation(instance);
+    }
+
+    // check to ensure the pluginType supports the model
+    if (!obj->activePlugin->supportsDrawingModel(model)) {
+        gLogI.log(instance, kError_ANPLogType, "------ %p Unsupported DrawingModel (%d)", instance, model);
+        return NPERR_GENERIC_ERROR;
+    }
+
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_Destroy(NPP instance, NPSavedData** save)
+{
+    PluginObject *obj = (PluginObject*) instance->pdata;
+    delete obj->activePlugin;
+
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_SetWindow(NPP instance, NPWindow* window)
+{
+    PluginObject *obj = (PluginObject*) instance->pdata;
+
+    // Do nothing if browser didn't support NPN_CreateObject which would have created the PluginObject.
+    if (obj != NULL) {
+        obj->window = window;
+    }
+
+    browser->invalidaterect(instance, NULL);
+
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype)
+{
+    *stype = NP_ASFILEONLY;
+    return NPERR_NO_ERROR;
+}
+
+NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
+{
+    return NPERR_NO_ERROR;
+}
+
+int32 NPP_WriteReady(NPP instance, NPStream* stream)
+{
+    return 0;
+}
+
+int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer)
+{
+    return 0;
+}
+
+void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
+{
+}
+
+void NPP_Print(NPP instance, NPPrint* platformPrint)
+{
+}
+
+int16 NPP_HandleEvent(NPP instance, void* event)
+{
+    PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata);
+    const ANPEvent* evt = reinterpret_cast<const ANPEvent*>(event);
+
+#if DEBUG_PLUGIN_EVENTS
+    switch (evt->eventType) {
+        case kDraw_ANPEventType:
+
+            if (evt->data.draw.model == kBitmap_ANPDrawingModel) {
+
+                static ANPBitmapFormat currentFormat = -1;
+                if (evt->data.draw.data.bitmap.format != currentFormat) {
+                    currentFormat = evt->data.draw.data.bitmap.format;
+                    gLogI.log(instance, kDebug_ANPLogType, "---- %p Draw (bitmap)"
+                              " clip=%d,%d,%d,%d format=%d", instance,
+                              evt->data.draw.clip.left,
+                              evt->data.draw.clip.top,
+                              evt->data.draw.clip.right,
+                              evt->data.draw.clip.bottom,
+                              evt->data.draw.data.bitmap.format);
+                }
+            }
+            break;
+
+        case kKey_ANPEventType:
+            gLogI.log(instance, kDebug_ANPLogType, "---- %p Key action=%d"
+                      " code=%d vcode=%d unichar=%d repeat=%d mods=%x", instance,
+                      evt->data.key.action,
+                      evt->data.key.nativeCode,
+                      evt->data.key.virtualCode,
+                      evt->data.key.unichar,
+                      evt->data.key.repeatCount,
+                      evt->data.key.modifiers);
+            break;
+
+        case kLifecycle_ANPEventType:
+            gLogI.log(instance, kDebug_ANPLogType, "---- %p Lifecycle action=%d",
+                                instance, evt->data.lifecycle.action);
+            break;
+
+       case kTouch_ANPEventType:
+            gLogI.log(instance, kDebug_ANPLogType, "---- %p Touch action=%d [%d %d]",
+                      instance, evt->data.touch.action, evt->data.touch.x,
+                      evt->data.touch.y);
+            break;
+
+       case kMouse_ANPEventType:
+            gLogI.log(instance, kDebug_ANPLogType, "---- %p Mouse action=%d [%d %d]",
+                      instance, evt->data.mouse.action, evt->data.mouse.x,
+                      evt->data.mouse.y);
+            break;
+
+       case kVisibleRect_ANPEventType:
+            gLogI.log(instance, kDebug_ANPLogType, "---- %p VisibleRect [%d %d %d %d]",
+                      instance, evt->data.visibleRect.rect.left, evt->data.visibleRect.rect.top,
+                      evt->data.visibleRect.rect.right, evt->data.visibleRect.rect.bottom);
+            break;
+
+        default:
+            gLogI.log(instance, kError_ANPLogType, "---- %p Unknown Event [%d]",
+                      instance, evt->eventType);
+            break;
+    }
+#endif
+
+    if(!obj->activePlugin) {
+        gLogI.log(instance, kError_ANPLogType, "the active plugin is null.");
+        return 0; // unknown or unhandled event
+    }
+    else {
+        return obj->activePlugin->handleEvent(evt);
+    }
+}
+
+void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
+{
+
+}
+
+EXPORT NPError NP_GetValue(NPP instance, NPPVariable variable, void *value) {
+
+    if (variable == NPPVpluginNameString) {
+        const char **str = (const char **)value;
+        *str = "Test Plugin";
+        return NPERR_NO_ERROR;
+    }
+
+    if (variable == NPPVpluginDescriptionString) {
+        const char **str = (const char **)value;
+        *str = "Description of Test Plugin";
+        return NPERR_NO_ERROR;
+    }
+
+    return NPERR_GENERIC_ERROR;
+}
+
+NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
+{
+    if (variable == NPPVpluginScriptableNPObject) {
+        void **v = (void **)value;
+        PluginObject *obj = (PluginObject*) instance->pdata;
+
+        if (obj)
+            browser->retainobject((NPObject*)obj);
+
+        *v = obj;
+        return NPERR_NO_ERROR;
+    }
+
+    return NPERR_GENERIC_ERROR;
+}
+
+NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value)
+{
+    return NPERR_GENERIC_ERROR;
+}
+
diff --git a/samples/BrowserPlugin/jni/main.h b/samples/BrowserPlugin/jni/main.h
new file mode 100644
index 0000000..4532911
--- /dev/null
+++ b/samples/BrowserPlugin/jni/main.h
@@ -0,0 +1,31 @@
+/*
+ * 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include <npapi.h>
+#include <npfunctions.h>
+#include <npruntime.h>
+#include "android_npapi.h"
+
+extern NPNetscapeFuncs* browser;
diff --git a/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp b/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
new file mode 100644
index 0000000..a34870f
--- /dev/null
+++ b/samples/BrowserPlugin/jni/paint/PaintPlugin.cpp
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2009, 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "PaintPlugin.h"
+
+#include <fcntl.h>
+#include <math.h>
+#include <string.h>
+
+extern NPNetscapeFuncs*         browser;
+extern ANPLogInterfaceV0        gLogI;
+extern ANPCanvasInterfaceV0     gCanvasI;
+extern ANPPaintInterfaceV0      gPaintI;
+extern ANPPathInterfaceV0       gPathI;
+extern ANPSurfaceInterfaceV0    gSurfaceI;
+extern ANPTypefaceInterfaceV0   gTypefaceI;
+
+///////////////////////////////////////////////////////////////////////////////
+
+PaintPlugin::PaintPlugin(NPP inst) : SubPlugin(inst) {
+
+    m_isTouchActive = false;
+    m_isTouchCurrentInput = true;
+    m_activePaintColor = s_redColor;
+
+    memset(&m_drawingSurface, 0, sizeof(m_drawingSurface));
+    memset(&m_inputToggle,  0, sizeof(m_inputToggle));
+    memset(&m_colorToggle, 0, sizeof(m_colorToggle));
+    memset(&m_clearSurface,  0, sizeof(m_clearSurface));
+
+    // initialize the drawing surface
+    m_surfaceReady = false;
+    m_surface = gSurfaceI.newRasterSurface(inst, kRGBA_8888_ANPBitmapFormat, true);
+    if(!m_surface)
+        gLogI.log(inst, kError_ANPLogType, "----%p Unable to create RGBA surface", inst);
+
+    // initialize the path
+    m_touchPath = gPathI.newPath();
+    if(!m_touchPath)
+        gLogI.log(inst, kError_ANPLogType, "----%p Unable to create the touch path", inst);
+
+    // initialize the paint colors
+    m_paintSurface = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintSurface, gPaintI.getFlags(m_paintSurface) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintSurface, 0xFFC0C0C0);
+    gPaintI.setTextSize(m_paintSurface, 18);
+
+    m_paintButton = gPaintI.newPaint();
+    gPaintI.setFlags(m_paintButton, gPaintI.getFlags(m_paintButton) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(m_paintButton, 0xFFA8A8A8);
+
+    // initialize the typeface (set the colors)
+    ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle);
+    gPaintI.setTypeface(m_paintSurface, tf);
+    gTypefaceI.unref(tf);
+
+    //register for touch events
+    ANPEventFlags flags = kTouch_ANPEventFlag;
+    NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags);
+    if (err != NPERR_NO_ERROR) {
+        gLogI.log(inst, kError_ANPLogType, "Error selecting input events.");
+    }
+}
+
+PaintPlugin::~PaintPlugin() {
+    gSurfaceI.deleteSurface(m_surface);
+    gPathI.deletePath(m_touchPath);
+    gPaintI.deletePaint(m_paintSurface);
+    gPaintI.deletePaint(m_paintButton);
+}
+
+bool PaintPlugin::supportsDrawingModel(ANPDrawingModel model) {
+    return (model == kSurface_ANPDrawingModel);
+}
+
+ANPCanvas* PaintPlugin::getCanvas(ANPRectI* dirtyRect) {
+
+    ANPBitmap bitmap;
+    if (!m_surfaceReady || !gSurfaceI.lock(m_surface, &bitmap, dirtyRect))
+        return NULL;
+
+    ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap);
+
+    // clip the canvas to the dirty rect b/c the surface is only required to
+    // copy a minimum of the dirty rect and may copy more. The clipped canvas
+    // however will never write to pixels outside of the clipped area.
+    if (dirtyRect) {
+        ANPRectF clipR;
+        clipR.left = dirtyRect->left;
+        clipR.top = dirtyRect->top;
+        clipR.right = dirtyRect->right;
+        clipR.bottom = dirtyRect->bottom;
+        gCanvasI.clipRect(canvas, &clipR);
+    }
+
+    return canvas;
+}
+
+ANPCanvas* PaintPlugin::getCanvas(ANPRectF* dirtyRect) {
+
+    ANPRectI newRect;
+    newRect.left = (int) dirtyRect->left;
+    newRect.top = (int) dirtyRect->top;
+    newRect.right = (int) dirtyRect->right;
+    newRect.bottom = (int) dirtyRect->bottom;
+
+    return getCanvas(&newRect);
+}
+
+void PaintPlugin::releaseCanvas(ANPCanvas* canvas) {
+    gSurfaceI.unlock(m_surface);
+    gCanvasI.deleteCanvas(canvas);
+}
+
+void PaintPlugin::drawCleanPlugin(ANPCanvas* canvas) {
+    NPP instance = this->inst();
+    PluginObject *obj = (PluginObject*) instance->pdata;
+
+    // if no canvas get a locked canvas
+    if (!canvas)
+        canvas = getCanvas();
+
+    if (!canvas)
+        return;
+
+    const float buttonWidth = 60;
+    const float buttonHeight = 30;
+    const int W = obj->window->width;
+    const int H = obj->window->height;
+
+    // color the plugin canvas
+    gCanvasI.drawColor(canvas, 0xFFCDCDCD);
+
+    // get font metrics
+    ANPFontMetrics fontMetrics;
+    gPaintI.getFontMetrics(m_paintSurface, &fontMetrics);
+
+    // draw the input toggle button
+    m_inputToggle.left = 5;
+    m_inputToggle.top = H - buttonHeight - 5;
+    m_inputToggle.right = m_inputToggle.left + buttonWidth;
+    m_inputToggle.bottom = m_inputToggle.top + buttonHeight;
+    gCanvasI.drawRect(canvas, &m_inputToggle, m_paintButton);
+    const char* inputText = m_isTouchCurrentInput ? "Touch" : "Mouse";
+    gCanvasI.drawText(canvas, inputText, strlen(inputText), m_inputToggle.left + 5,
+                      m_inputToggle.top - fontMetrics.fTop, m_paintSurface);
+
+    // draw the color selector button
+    m_colorToggle.left = (W/2) - (buttonWidth/2);
+    m_colorToggle.top = H - buttonHeight - 5;
+    m_colorToggle.right = m_colorToggle.left + buttonWidth;
+    m_colorToggle.bottom = m_colorToggle.top + buttonHeight;
+    gCanvasI.drawRect(canvas, &m_colorToggle, m_paintButton);
+    const char* colorText = getColorText();
+    gCanvasI.drawText(canvas, colorText, strlen(colorText), m_colorToggle.left + 5,
+                      m_colorToggle.top - fontMetrics.fTop, m_paintSurface);
+
+    // draw the clear canvas button
+    m_clearSurface.left = W - buttonWidth - 5;
+    m_clearSurface.top = H - buttonHeight - 5;
+    m_clearSurface.right = m_clearSurface.left + buttonWidth;
+    m_clearSurface.bottom = m_clearSurface.top + buttonHeight;
+    gCanvasI.drawRect(canvas, &m_clearSurface, m_paintButton);
+    const char* clearText = "Clear";
+    gCanvasI.drawText(canvas, clearText, strlen(clearText), m_clearSurface.left + 5,
+                      m_clearSurface.top - fontMetrics.fTop, m_paintSurface);
+
+    // draw the drawing surface box (5 px from the edge)
+    m_drawingSurface.left = 5;
+    m_drawingSurface.top = 5;
+    m_drawingSurface.right = W - 5;
+    m_drawingSurface.bottom = m_colorToggle.top - 5;
+    gCanvasI.drawRect(canvas, &m_drawingSurface, m_paintSurface);
+
+    // release the canvas
+    releaseCanvas(canvas);
+}
+
+const char* PaintPlugin::getColorText() {
+
+    if (m_activePaintColor == s_blueColor)
+        return "Blue";
+    else if (m_activePaintColor == s_greenColor)
+        return "Green";
+    else
+        return "Red";
+}
+
+int16 PaintPlugin::handleEvent(const ANPEvent* evt) {
+    switch (evt->eventType) {
+        case kSurface_ANPEventType:
+            switch (evt->data.surface.action) {
+                case kCreated_ANPSurfaceAction:
+                    m_surfaceReady = true;
+                    drawCleanPlugin();
+                    return 1;
+                case kDestroyed_ANPSurfaceAction:
+                    m_surfaceReady = false;
+                    return 1;
+                case kChanged_ANPSurfaceAction:
+                    // get the plugin's dimensions according to the DOM
+                    PluginObject *obj = (PluginObject*) inst()->pdata;
+                    const int pW = obj->window->width;
+                    const int pH = obj->window->height;
+                    // get the plugin's surface dimensions
+                    const int sW = evt->data.surface.data.changed.width;
+                    const int sH = evt->data.surface.data.changed.height;
+                    if (pW != sW || pH != sH)
+                        gLogI.log(inst(), kError_ANPLogType,
+                                  "----%p Invalid Surface Dimensions (%d,%d):(%d,%d)",
+                                  inst(), pW, pH, sW, sH);
+                    return 1;
+            }
+            break;
+
+        case kTouch_ANPEventType: {
+            float x = (float) evt->data.touch.x;
+            float y = (float) evt->data.touch.y;
+            if (kDown_ANPTouchAction == evt->data.touch.action && m_isTouchCurrentInput) {
+
+                ANPRectF* rect = validTouch(evt->data.touch.x, evt->data.touch.y);
+                if(rect == &m_drawingSurface) {
+                    m_isTouchActive = true;
+                    gPathI.moveTo(m_touchPath, x, y);
+                    paintTouch();
+                    return 1;
+                }
+
+            } else if (kMove_ANPTouchAction == evt->data.touch.action && m_isTouchActive) {
+                gPathI.lineTo(m_touchPath, x, y);
+                paintTouch();
+                return 1;
+            } else if (kUp_ANPTouchAction == evt->data.touch.action && m_isTouchActive) {
+                gPathI.lineTo(m_touchPath, x, y);
+                paintTouch();
+                m_isTouchActive = false;
+                gPathI.reset(m_touchPath);
+                return 1;
+            } else if (kCancel_ANPTouchAction == evt->data.touch.action) {
+                m_isTouchActive = false;
+                gPathI.reset(m_touchPath);
+                return 1;
+            }
+
+            break;
+        }
+        case kMouse_ANPEventType: {
+
+            if (m_isTouchActive)
+                gLogI.log(inst(), kError_ANPLogType, "----%p Received unintended mouse event", inst());
+
+            if (kDown_ANPMouseAction == evt->data.mouse.action) {
+                ANPRectF* rect = validTouch(evt->data.mouse.x, evt->data.mouse.y);
+                if (rect == &m_drawingSurface)
+                    paintMouse(evt->data.mouse.x, evt->data.mouse.y);
+                else if (rect == &m_inputToggle)
+                    toggleInputMethod();
+                else if (rect == &m_colorToggle)
+                    togglePaintColor();
+                else if (rect == &m_clearSurface)
+                    drawCleanPlugin();
+            }
+            return 1;
+        }
+        default:
+            break;
+    }
+    return 0;   // unknown or unhandled event
+}
+
+ANPRectF* PaintPlugin::validTouch(int x, int y) {
+
+    //convert to float
+    float fx = (int) x;
+    float fy = (int) y;
+
+    if (fx > m_drawingSurface.left && fx < m_drawingSurface.right && fy > m_drawingSurface.top && fy < m_drawingSurface.bottom)
+        return &m_drawingSurface;
+    else if (fx > m_inputToggle.left && fx < m_inputToggle.right && fy > m_inputToggle.top && fy < m_inputToggle.bottom)
+        return &m_inputToggle;
+    else if (fx > m_colorToggle.left && fx < m_colorToggle.right && fy > m_colorToggle.top && fy < m_colorToggle.bottom)
+        return &m_colorToggle;
+    else if (fx > m_clearSurface.left && fx < m_clearSurface.right && fy > m_clearSurface.top && fy < m_clearSurface.bottom)
+        return &m_clearSurface;
+    else
+        return NULL;
+}
+
+void PaintPlugin::toggleInputMethod() {
+    m_isTouchCurrentInput = !m_isTouchCurrentInput;
+
+    // lock only the input toggle and redraw the canvas
+    ANPCanvas* lockedCanvas = getCanvas(&m_inputToggle);
+    drawCleanPlugin(lockedCanvas);
+}
+
+void PaintPlugin::togglePaintColor() {
+    if (m_activePaintColor == s_blueColor)
+        m_activePaintColor = s_redColor;
+    else if (m_activePaintColor == s_greenColor)
+        m_activePaintColor = s_blueColor;
+    else
+        m_activePaintColor = s_greenColor;
+
+    // lock only the color toggle and redraw the canvas
+    ANPCanvas* lockedCanvas = getCanvas(&m_colorToggle);
+    drawCleanPlugin(lockedCanvas);
+}
+
+void PaintPlugin::paintMouse(int x, int y) {
+    //TODO do not paint outside the drawing surface
+
+    //create the paint color
+    ANPPaint* fillPaint = gPaintI.newPaint();
+    gPaintI.setFlags(fillPaint, gPaintI.getFlags(fillPaint) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setStyle(fillPaint, kFill_ANPPaintStyle);
+    gPaintI.setColor(fillPaint, m_activePaintColor);
+
+    // handle the simple "mouse" paint (draw a point)
+    ANPRectF point;
+    point.left =   (float) x-3;
+    point.top =    (float) y-3;
+    point.right =  (float) x+3;
+    point.bottom = (float) y+3;
+
+    // get a canvas that is only locked around the point and draw it
+    ANPCanvas* canvas = getCanvas(&point);
+    gCanvasI.drawOval(canvas, &point, fillPaint);
+
+    // clean up
+    releaseCanvas(canvas);
+    gPaintI.deletePaint(fillPaint);
+}
+
+void PaintPlugin::paintTouch() {
+    //TODO do not paint outside the drawing surface
+
+    //create the paint color
+    ANPPaint* strokePaint = gPaintI.newPaint();
+    gPaintI.setFlags(strokePaint, gPaintI.getFlags(strokePaint) | kAntiAlias_ANPPaintFlag);
+    gPaintI.setColor(strokePaint, m_activePaintColor);
+    gPaintI.setStyle(strokePaint, kStroke_ANPPaintStyle);
+    gPaintI.setStrokeWidth(strokePaint, 6.0);
+    gPaintI.setStrokeCap(strokePaint, kRound_ANPPaintCap);
+    gPaintI.setStrokeJoin(strokePaint, kRound_ANPPaintJoin);
+
+    // handle the complex "touch" paint (draw a line)
+    ANPRectF bounds;
+    gPathI.getBounds(m_touchPath, &bounds);
+
+    // get a canvas that is only locked around the point and draw the path
+    ANPCanvas* canvas = getCanvas(&bounds);
+    gCanvasI.drawPath(canvas, m_touchPath, strokePaint);
+
+    // clean up
+    releaseCanvas(canvas);
+    gPaintI.deletePaint(strokePaint);
+}
diff --git a/samples/BrowserPlugin/jni/paint/PaintPlugin.h b/samples/BrowserPlugin/jni/paint/PaintPlugin.h
new file mode 100644
index 0000000..7e6f235
--- /dev/null
+++ b/samples/BrowserPlugin/jni/paint/PaintPlugin.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2009, 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:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS 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.
+ */
+
+#include "PluginObject.h"
+#include <stdio.h>
+
+#ifndef paintPlugin__DEFINED
+#define paintPlugin__DEFINED
+
+class PaintPlugin : public SubPlugin {
+public:
+    PaintPlugin(NPP inst);
+    virtual ~PaintPlugin();
+    virtual bool supportsDrawingModel(ANPDrawingModel);
+    virtual int16 handleEvent(const ANPEvent* evt);
+private:
+    void        drawCleanPlugin(ANPCanvas* canvas = NULL);
+    ANPCanvas*  getCanvas(ANPRectI* dirtyRect = NULL);
+    ANPCanvas*  getCanvas(ANPRectF* dirtyRect);
+    const char* getColorText();
+    void        paintMouse(int x, int y);
+    void        paintTouch();
+    void        releaseCanvas(ANPCanvas*);
+    void        toggleInputMethod();
+    void        togglePaintColor();
+    ANPRectF*   validTouch(int x, int y);
+
+    bool        m_isTouchActive;
+    bool        m_isTouchCurrentInput;
+    bool        m_surfaceReady;
+
+    ANPSurface* m_surface;
+    ANPPath*    m_touchPath;
+
+    ANPRectF    m_drawingSurface;
+    ANPRectF    m_inputToggle;
+    ANPRectF    m_colorToggle;
+    ANPRectF    m_clearSurface;
+
+    ANPPaint*   m_paintSurface;
+    ANPPaint*   m_paintButton;
+
+    ANPColor    m_activePaintColor;
+    static const ANPColor s_redColor   = 0xFFFF0000;
+    static const ANPColor s_greenColor = 0xFF00FF00;
+    static const ANPColor s_blueColor  = 0xFF0000FF;
+};
+
+#endif // paintPlugin__DEFINED
diff --git a/samples/BrowserPlugin/res/drawable/sample_browser_plugin.png b/samples/BrowserPlugin/res/drawable/sample_browser_plugin.png
new file mode 100755
index 0000000..47c79d1
--- /dev/null
+++ b/samples/BrowserPlugin/res/drawable/sample_browser_plugin.png
Binary files differ
diff --git a/samples/BrowserPlugin/res/values/strings.xml b/samples/BrowserPlugin/res/values/strings.xml
new file mode 100644
index 0000000..1f8dd49
--- /dev/null
+++ b/samples/BrowserPlugin/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<resources>
+   <string name="sample_browser_plugin">Sample Browser Plugin</string>
+</resources>
diff --git a/samples/BrowserPlugin/src/com/android/sampleplugin/SamplePlugin.java b/samples/BrowserPlugin/src/com/android/sampleplugin/SamplePlugin.java
new file mode 100644
index 0000000..9b8ce95
--- /dev/null
+++ b/samples/BrowserPlugin/src/com/android/sampleplugin/SamplePlugin.java
@@ -0,0 +1,15 @@
+package com.android.sampleplugin;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class SamplePlugin extends Service {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
diff --git a/samples/FixedGridLayout/Android.mk b/samples/FixedGridLayout/Android.mk
new file mode 100644
index 0000000..1562cb9
--- /dev/null
+++ b/samples/FixedGridLayout/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FixedGridLayout
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/FixedGridLayout/AndroidManifest.xml b/samples/FixedGridLayout/AndroidManifest.xml
new file mode 100644
index 0000000..9acd309
--- /dev/null
+++ b/samples/FixedGridLayout/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.example.android.fixedgridlayout"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:label="@string/app_name">
+        <activity android:name=".FixedGridLayoutTest"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest> 
diff --git a/samples/FixedGridLayout/res/drawable/bugdroid.png b/samples/FixedGridLayout/res/drawable/bugdroid.png
new file mode 100644
index 0000000..855484a
--- /dev/null
+++ b/samples/FixedGridLayout/res/drawable/bugdroid.png
Binary files differ
diff --git a/samples/FixedGridLayout/res/layout/main.xml b/samples/FixedGridLayout/res/layout/main.xml
new file mode 100644
index 0000000..93e2d5e
--- /dev/null
+++ b/samples/FixedGridLayout/res/layout/main.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.example.android.fixedgridlayout.FixedGridLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res/com.example.android.fixedgridlayout"
+        android:id="@+id/grid"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        app:cellWidth="80dp"
+        app:cellHeight="100dp"
+        >
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/bugdroid"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/bugdroid"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/bugdroid"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/bugdroid"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/bugdroid"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/bugdroid"
+        />
+</com.example.android.fixedgridlayout.FixedGridLayout>
+
diff --git a/samples/FixedGridLayout/res/values/attrs.xml b/samples/FixedGridLayout/res/values/attrs.xml
new file mode 100644
index 0000000..8287d0e
--- /dev/null
+++ b/samples/FixedGridLayout/res/values/attrs.xml
@@ -0,0 +1,7 @@
+<resources>
+    <declare-styleable name="FixedGridLayout">
+        <attr name="cellWidth" format="dimension" />
+        <attr name="cellHeight" format="dimension" />
+    </declare-styleable>
+</resources>
+
diff --git a/samples/FixedGridLayout/res/values/strings.xml b/samples/FixedGridLayout/res/values/strings.xml
new file mode 100644
index 0000000..14563c7
--- /dev/null
+++ b/samples/FixedGridLayout/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">FixedGridLayoutTest</string>
+</resources>
diff --git a/samples/FixedGridLayout/src/com/example/android/fixedgridlayout/FixedGridLayout.java b/samples/FixedGridLayout/src/com/example/android/fixedgridlayout/FixedGridLayout.java
new file mode 100644
index 0000000..413157f
--- /dev/null
+++ b/samples/FixedGridLayout/src/com/example/android/fixedgridlayout/FixedGridLayout.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.fixedgridlayout;
+
+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;
+
+/**
+ * A layout that arranges its children in a grid.  The size of the
+ * cells is set by the {@link #setCellSize} method and the
+ * android:cell_width and android:cell_height attributes in XML.
+ * The number of rows and columns is determined at runtime.  Each
+ * cell contains exactly one view, and they flow in the natural
+ * child order (the order in which they were added, or the index
+ * in {@link #addViewAt}.  Views can not span multiple cells.
+ */
+public class FixedGridLayout extends ViewGroup {
+    int mCellWidth;
+    int mCellHeight;
+
+    public FixedGridLayout(Context context) {
+        super(context);
+    }
+
+    public FixedGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // Read the resource attributes.
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.FixedGridLayout);
+        mCellWidth = a.getDimensionPixelSize(
+                R.styleable.FixedGridLayout_cellWidth, -1);
+        mCellHeight = a.getDimensionPixelSize(
+                R.styleable.FixedGridLayout_cellHeight, -1);
+        a.recycle();
+    }
+
+    public void setCellWidth(int px) {
+        mCellWidth = px;
+        requestLayout();
+    }
+
+    public void setCellHeight(int px) {
+        mCellHeight = px;
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int cellWidthSpec = MeasureSpec.makeMeasureSpec(mCellWidth,
+                MeasureSpec.AT_MOST);
+        int cellHeightSpec = MeasureSpec.makeMeasureSpec(mCellHeight,
+                MeasureSpec.AT_MOST);
+
+        int count = getChildCount();
+        for (int index=0; index<count; index++) {
+            final View child = getChildAt(index);
+            child.measure(cellWidthSpec, cellHeightSpec);
+        }
+        // Use the size our parents gave us
+        setMeasuredDimension(resolveSize(mCellWidth*count, widthMeasureSpec),
+                resolveSize(mCellHeight*count, heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        int cellWidth = mCellWidth;
+        int cellHeight = mCellHeight;
+        int columns = (r - l) / cellWidth;
+        if (columns < 0) {
+            columns = 1;
+        }
+        int x = 0;
+        int y = 0;
+        int i = 0;
+        int count = getChildCount();
+        for (int index=0; index<count; index++) {
+            final View child = getChildAt(index);
+
+            int w = child.getMeasuredWidth();
+            int h = child.getMeasuredHeight();
+
+            int left = x + ((cellWidth-w)/2);
+            int top = y + ((cellHeight-h)/2);
+
+            child.layout(left, top, left+w, top+h);
+            if (i >= (columns-1)) {
+                // advance to next row
+                i = 0;
+                x = 0;
+                y += cellHeight;
+            } else {
+                i++;
+                x += cellWidth;
+            }
+        }
+    }
+}
+
diff --git a/samples/FixedGridLayout/src/com/example/android/fixedgridlayout/FixedGridLayoutTest.java b/samples/FixedGridLayout/src/com/example/android/fixedgridlayout/FixedGridLayoutTest.java
new file mode 100644
index 0000000..19c9da7
--- /dev/null
+++ b/samples/FixedGridLayout/src/com/example/android/fixedgridlayout/FixedGridLayoutTest.java
@@ -0,0 +1,19 @@
+package com.example.android.fixedgridlayout;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class FixedGridLayoutTest extends Activity
+{
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        FixedGridLayout grid = (FixedGridLayout)findViewById(R.id.grid);
+        grid.setCellWidth(80);
+        grid.setCellHeight(80);
+    }
+}
diff --git a/samples/Home/src/com/example/android/home/Home.java b/samples/Home/src/com/example/android/home/Home.java
index c23f7b8..7cae87e 100644
--- a/samples/Home/src/com/example/android/home/Home.java
+++ b/samples/Home/src/com/example/android/home/Home.java
@@ -110,6 +110,9 @@
 
     private boolean mBlockAnimation;
 
+    private boolean mHomeDown;
+    private boolean mBackDown;
+    
     private View mShowApplications;
     private CheckBox mShowApplicationsCheck;
 
@@ -396,19 +399,44 @@
     }
 
     @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (!hasFocus) {
+            mBackDown = mHomeDown = false;
+        }
+    }
+
+    @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             switch (event.getKeyCode()) {
                 case KeyEvent.KEYCODE_BACK:
+                    mBackDown = true;
                     return true;
                 case KeyEvent.KEYCODE_HOME:
+                    mHomeDown = true;
+                    return true;
+            }
+        } else if (event.getAction() == KeyEvent.ACTION_UP) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_BACK:
+                    if (!event.isCanceled()) {
+                        // Do BACK behavior.
+                    }
+                    mBackDown = true;
+                    return true;
+                case KeyEvent.KEYCODE_HOME:
+                    if (!event.isCanceled()) {
+                        // Do HOME behavior.
+                    }
+                    mHomeDown = true;
                     return true;
             }
         }
 
         return super.dispatchKeyEvent(event);
     }
-
+    
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         super.onCreateOptionsMenu(menu);
diff --git a/simulator/app/Android.mk b/simulator/app/Android.mk
index c6e1d14..3ce7cd5 100644
--- a/simulator/app/Android.mk
+++ b/simulator/app/Android.mk
@@ -24,12 +24,15 @@
 	PhoneCollection.cpp \
 	PhoneData.cpp \
 	PhoneWindow.cpp \
+	Pipe.cpp \
 	Preferences.cpp \
 	PrefsDialog.cpp \
 	PropertyServer.cpp \
 	Semaphore.cpp \
 	Shmem.cpp \
-	UserEvent.cpp
+	UserEvent.cpp \
+	executablepath_linux.cpp \
+	ported.cpp
 
 LOCAL_STATIC_LIBRARIES := \
 	libtinyxml
diff --git a/simulator/app/LoadableImage.cpp b/simulator/app/LoadableImage.cpp
index e5bd0f3..513165b 100644
--- a/simulator/app/LoadableImage.cpp
+++ b/simulator/app/LoadableImage.cpp
@@ -17,7 +17,7 @@
 #include "AssetStream.h"
 #include "MyApp.h"
 
-#include <utils.h>
+#include "utils.h"
 
 #include <stdio.h>
 
diff --git a/simulator/app/LocalBiChannel.h b/simulator/app/LocalBiChannel.h
index ce04bc0..a4f4d62 100644
--- a/simulator/app/LocalBiChannel.h
+++ b/simulator/app/LocalBiChannel.h
@@ -10,7 +10,7 @@
 #error DO NOT USE THIS FILE IN THE DEVICE BUILD
 #endif
 
-#include <utils/Pipe.h>
+#include "Pipe.h"
 
 namespace android {
 
diff --git a/simulator/app/MessageStream.h b/simulator/app/MessageStream.h
index de9c398..82a9b4c 100644
--- a/simulator/app/MessageStream.h
+++ b/simulator/app/MessageStream.h
@@ -17,7 +17,7 @@
 #error DO NOT USE THIS FILE IN THE DEVICE BUILD
 #endif
 
-#include <utils/Pipe.h>
+#include "Pipe.h"
 #include <stdlib.h>
 #include <cutils/uio.h>
 
diff --git a/simulator/app/MyApp.cpp b/simulator/app/MyApp.cpp
index 313e44d..fd610b1 100644
--- a/simulator/app/MyApp.cpp
+++ b/simulator/app/MyApp.cpp
@@ -16,7 +16,7 @@
 
 #include "MainFrame.h"
 #include "MyApp.h"
-#include <utils/executablepath.h>
+#include "executablepath.h"
 
 #include <stdio.h>
 #include <unistd.h>
diff --git a/simulator/app/PhoneCollection.cpp b/simulator/app/PhoneCollection.cpp
index 5cddfa8..e1882cc 100644
--- a/simulator/app/PhoneCollection.cpp
+++ b/simulator/app/PhoneCollection.cpp
@@ -18,7 +18,7 @@
 #include "PhoneData.h"
 #include "MyApp.h"
 
-#include <utils.h>
+#include "utils.h"
 
 #include <stdlib.h>
 #include <unistd.h>
diff --git a/simulator/app/PhoneData.cpp b/simulator/app/PhoneData.cpp
index 48614fd..f15df2b 100644
--- a/simulator/app/PhoneData.cpp
+++ b/simulator/app/PhoneData.cpp
@@ -19,7 +19,7 @@
 #include "PhoneCollection.h"
 #include "MyApp.h"
 
-#include <utils.h>
+#include "utils.h"
 #include <utils/AssetManager.h>
 #include <utils/String8.h>
 
diff --git a/simulator/app/PhoneData.h b/simulator/app/PhoneData.h
index c7f4732..2d7003a 100644
--- a/simulator/app/PhoneData.h
+++ b/simulator/app/PhoneData.h
@@ -22,7 +22,7 @@
 #include "PhoneButton.h"
 #include "LoadableImage.h"
 #include <ui/PixelFormat.h>
-#include <utils.h>
+#include "utils.h"
 
 
 /*
diff --git a/simulator/app/Pipe.cpp b/simulator/app/Pipe.cpp
new file mode 100644
index 0000000..05ce790
--- /dev/null
+++ b/simulator/app/Pipe.cpp
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Unidirectional pipe.
+//
+
+#include "Pipe.h"
+#include <utils/Log.h>
+
+#if defined(HAVE_WIN32_IPC)
+# include <windows.h>
+#else
+# include <fcntl.h>
+# include <unistd.h>
+# include <errno.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+using namespace android;
+
+const unsigned long kInvalidHandle = (unsigned long) -1;
+
+
+/*
+ * Constructor.  Do little.
+ */
+Pipe::Pipe(void)
+    : mReadNonBlocking(false), mReadHandle(kInvalidHandle),
+      mWriteHandle(kInvalidHandle)
+{
+}
+
+/*
+ * Destructor.  Use the system-appropriate close call.
+ */
+Pipe::~Pipe(void)
+{
+#if defined(HAVE_WIN32_IPC)
+    if (mReadHandle != kInvalidHandle) {
+        if (!CloseHandle((HANDLE)mReadHandle))
+            LOG(LOG_WARN, "pipe", "failed closing read handle (%ld)\n",
+                mReadHandle);
+    }
+    if (mWriteHandle != kInvalidHandle) {
+        FlushFileBuffers((HANDLE)mWriteHandle);
+        if (!CloseHandle((HANDLE)mWriteHandle))
+            LOG(LOG_WARN, "pipe", "failed closing write handle (%ld)\n",
+                mWriteHandle);
+    }
+#else
+    if (mReadHandle != kInvalidHandle) {
+        if (close((int) mReadHandle) != 0)
+            LOG(LOG_WARN, "pipe", "failed closing read fd (%d)\n",
+                (int) mReadHandle);
+    }
+    if (mWriteHandle != kInvalidHandle) {
+        if (close((int) mWriteHandle) != 0)
+            LOG(LOG_WARN, "pipe", "failed closing write fd (%d)\n",
+                (int) mWriteHandle);
+    }
+#endif
+}
+
+/*
+ * Create the pipe.
+ *
+ * Use the POSIX stuff for everything but Windows.
+ */
+bool Pipe::create(void)
+{
+    assert(mReadHandle == kInvalidHandle);
+    assert(mWriteHandle == kInvalidHandle);
+
+#if defined(HAVE_WIN32_IPC)
+    /* we use this across processes, so they need to be inheritable */
+    HANDLE handles[2];
+    SECURITY_ATTRIBUTES saAttr;
+
+    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+    saAttr.bInheritHandle = TRUE;
+    saAttr.lpSecurityDescriptor = NULL;
+
+    if (!CreatePipe(&handles[0], &handles[1], &saAttr, 0)) {
+        LOG(LOG_ERROR, "pipe", "unable to create pipe\n");
+        return false;
+    }
+    mReadHandle = (unsigned long) handles[0];
+    mWriteHandle = (unsigned long) handles[1];
+    return true;
+#else
+    int fds[2];
+
+    if (pipe(fds) != 0) {
+        LOG(LOG_ERROR, "pipe", "unable to create pipe\n");
+        return false;
+    }
+    mReadHandle = fds[0];
+    mWriteHandle = fds[1];
+    return true;
+#endif
+}
+
+/*
+ * Create a "half pipe".  Please, no Segway riding.
+ */
+bool Pipe::createReader(unsigned long handle)
+{
+    mReadHandle = handle;
+    assert(mWriteHandle == kInvalidHandle);
+    return true;
+}
+
+/*
+ * Create a "half pipe" for writing.
+ */
+bool Pipe::createWriter(unsigned long handle)
+{
+    mWriteHandle = handle;
+    assert(mReadHandle == kInvalidHandle);
+    return true;
+}
+
+/*
+ * Return "true" if create() has been called successfully.
+ */
+bool Pipe::isCreated(void)
+{
+    // one or the other should be open
+    return (mReadHandle != kInvalidHandle || mWriteHandle != kInvalidHandle);
+}
+
+
+/*
+ * Read data from the pipe.
+ *
+ * For Linux and Darwin, just call read().  For Windows, implement
+ * non-blocking reads by calling PeekNamedPipe first.
+ */
+int Pipe::read(void* buf, int count)
+{
+    assert(mReadHandle != kInvalidHandle);
+
+#if defined(HAVE_WIN32_IPC)
+    DWORD totalBytesAvail = count;
+    DWORD bytesRead;
+
+    if (mReadNonBlocking) {
+        // use PeekNamedPipe to adjust read count expectations
+        if (!PeekNamedPipe((HANDLE) mReadHandle, NULL, 0, NULL,
+                &totalBytesAvail, NULL))
+        {
+            LOG(LOG_ERROR, "pipe", "PeekNamedPipe failed\n");
+            return -1;
+        }
+
+        if (totalBytesAvail == 0)
+            return 0;
+    }
+
+    if (!ReadFile((HANDLE) mReadHandle, buf, totalBytesAvail, &bytesRead,
+            NULL))
+    {
+        DWORD err = GetLastError();
+        if (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE)
+            return 0;
+        LOG(LOG_ERROR, "pipe", "ReadFile failed (err=%ld)\n", err);
+        return -1;
+    }
+
+    return (int) bytesRead;
+#else
+    int cc;
+    cc = ::read(mReadHandle, buf, count);
+    if (cc < 0 && errno == EAGAIN)
+        return 0;
+    return cc;
+#endif
+}
+
+/*
+ * Write data to the pipe.
+ *
+ * POSIX systems are trivial, Windows uses a different call and doesn't
+ * handle non-blocking writes.
+ *
+ * If we add non-blocking support here, we probably want to make it an
+ * all-or-nothing write.
+ *
+ * DO NOT use LOG() here, we could be writing a log message.
+ */
+int Pipe::write(const void* buf, int count)
+{
+    assert(mWriteHandle != kInvalidHandle);
+
+#if defined(HAVE_WIN32_IPC)
+    DWORD bytesWritten;
+
+    if (mWriteNonBlocking) {
+        // BUG: can't use PeekNamedPipe() to get the amount of space
+        // left.  Looks like we need to use "overlapped I/O" functions.
+        // I just don't care that much.
+    }
+
+    if (!WriteFile((HANDLE) mWriteHandle, buf, count, &bytesWritten, NULL)) {
+        // can't LOG, use stderr
+        fprintf(stderr, "WriteFile failed (err=%ld)\n", GetLastError());
+        return -1;
+    }
+
+    return (int) bytesWritten;
+#else
+    int cc;
+    cc = ::write(mWriteHandle, buf, count);
+    if (cc < 0 && errno == EAGAIN)
+        return 0;
+    return cc;
+#endif
+}
+
+/*
+ * Figure out if there is data available on the read fd.
+ *
+ * We return "true" on error because we want the caller to try to read
+ * from the pipe.  They'll notice the read failure and do something
+ * appropriate.
+ */
+bool Pipe::readReady(void)
+{
+    assert(mReadHandle != kInvalidHandle);
+
+#if defined(HAVE_WIN32_IPC)
+    DWORD totalBytesAvail;
+
+    if (!PeekNamedPipe((HANDLE) mReadHandle, NULL, 0, NULL,
+            &totalBytesAvail, NULL))
+    {
+        LOG(LOG_ERROR, "pipe", "PeekNamedPipe failed\n");
+        return true;
+    }
+
+    return (totalBytesAvail != 0);
+#else
+    errno = 0;
+    fd_set readfds;
+    struct timeval tv = { 0, 0 };
+    int cc;
+
+    FD_ZERO(&readfds);
+    FD_SET(mReadHandle, &readfds);
+
+    cc = select(mReadHandle+1, &readfds, NULL, NULL, &tv);
+    if (cc < 0) {
+        LOG(LOG_ERROR, "pipe", "select() failed\n");
+        return true;
+    } else if (cc == 0) {
+        /* timed out, nothing available */
+        return false;
+    } else if (cc == 1) {
+        /* our fd is ready */
+        return true;
+    } else {
+        LOG(LOG_ERROR, "pipe", "HUH? select() returned > 1\n");
+        return true;
+    }
+#endif
+}
+
+/*
+ * Enable or disable non-blocking mode for the read descriptor.
+ *
+ * NOTE: the calls succeed under Mac OS X, but the pipe doesn't appear to
+ * actually be in non-blocking mode.  If this matters -- i.e. you're not
+ * using a select() call -- put a call to readReady() in front of the
+ * ::read() call, with a PIPE_NONBLOCK_BROKEN #ifdef in the Makefile for
+ * Darwin.
+ */
+bool Pipe::setReadNonBlocking(bool val)
+{
+    assert(mReadHandle != kInvalidHandle);
+
+#if defined(HAVE_WIN32_IPC)
+    // nothing to do
+#else
+    int flags;
+
+    if (fcntl(mReadHandle, F_GETFL, &flags) == -1) {
+        LOG(LOG_ERROR, "pipe", "couldn't get flags for pipe read fd\n");
+        return false;
+    }
+    if (val)
+        flags |= O_NONBLOCK;
+    else
+        flags &= ~(O_NONBLOCK);
+    if (fcntl(mReadHandle, F_SETFL, &flags) == -1) {
+        LOG(LOG_ERROR, "pipe", "couldn't set flags for pipe read fd\n");
+        return false;
+    }
+#endif
+
+    mReadNonBlocking = val;
+    return true;
+}
+
+/*
+ * Enable or disable non-blocking mode for the write descriptor.
+ *
+ * As with setReadNonBlocking(), this does not work on the Mac.
+ */
+bool Pipe::setWriteNonBlocking(bool val)
+{
+    assert(mWriteHandle != kInvalidHandle);
+
+#if defined(HAVE_WIN32_IPC)
+    // nothing to do
+#else
+    int flags;
+
+    if (fcntl(mWriteHandle, F_GETFL, &flags) == -1) {
+        LOG(LOG_WARN, "pipe",
+            "Warning: couldn't get flags for pipe write fd (errno=%d)\n",
+            errno);
+        return false;
+    }
+    if (val)
+        flags |= O_NONBLOCK;
+    else
+        flags &= ~(O_NONBLOCK);
+    if (fcntl(mWriteHandle, F_SETFL, &flags) == -1) {
+        LOG(LOG_WARN, "pipe",
+            "Warning: couldn't set flags for pipe write fd (errno=%d)\n",
+            errno);
+        return false;
+    }
+#endif
+
+    mWriteNonBlocking = val;
+    return true;
+}
+
+/*
+ * Specify whether a file descriptor can be inherited by a child process.
+ * Under Linux this means setting the close-on-exec flag, under Windows
+ * this is SetHandleInformation(HANDLE_FLAG_INHERIT).
+ */
+bool Pipe::disallowReadInherit(void)
+{
+    if (mReadHandle == kInvalidHandle)
+        return false;
+
+#if defined(HAVE_WIN32_IPC)
+    if (SetHandleInformation((HANDLE) mReadHandle, HANDLE_FLAG_INHERIT, 0) == 0)
+        return false;
+#else
+    if (fcntl((int) mReadHandle, F_SETFD, FD_CLOEXEC) != 0)
+        return false;
+#endif
+    return true;
+}
+bool Pipe::disallowWriteInherit(void)
+{
+    if (mWriteHandle == kInvalidHandle)
+        return false;
+
+#if defined(HAVE_WIN32_IPC)
+    if (SetHandleInformation((HANDLE) mWriteHandle, HANDLE_FLAG_INHERIT, 0) == 0)
+        return false;
+#else
+    if (fcntl((int) mWriteHandle, F_SETFD, FD_CLOEXEC) != 0)
+        return false;
+#endif
+    return true;
+}
+
+/*
+ * Close read descriptor.
+ */
+bool Pipe::closeRead(void)
+{
+    if (mReadHandle == kInvalidHandle)
+        return false;
+
+#if defined(HAVE_WIN32_IPC)
+    if (mReadHandle != kInvalidHandle) {
+        if (!CloseHandle((HANDLE)mReadHandle)) {
+            LOG(LOG_WARN, "pipe", "failed closing read handle\n");
+            return false;
+        }
+    }
+#else
+    if (mReadHandle != kInvalidHandle) {
+        if (close((int) mReadHandle) != 0) {
+            LOG(LOG_WARN, "pipe", "failed closing read fd\n");
+            return false;
+        }
+    }
+#endif
+    mReadHandle = kInvalidHandle;
+    return true;
+}
+
+/*
+ * Close write descriptor.
+ */
+bool Pipe::closeWrite(void)
+{
+    if (mWriteHandle == kInvalidHandle)
+        return false;
+
+#if defined(HAVE_WIN32_IPC)
+    if (mWriteHandle != kInvalidHandle) {
+        if (!CloseHandle((HANDLE)mWriteHandle)) {
+            LOG(LOG_WARN, "pipe", "failed closing write handle\n");
+            return false;
+        }
+    }
+#else
+    if (mWriteHandle != kInvalidHandle) {
+        if (close((int) mWriteHandle) != 0) {
+            LOG(LOG_WARN, "pipe", "failed closing write fd\n");
+            return false;
+        }
+    }
+#endif
+    mWriteHandle = kInvalidHandle;
+    return true;
+}
+
+/*
+ * Get the read handle.
+ */
+unsigned long Pipe::getReadHandle(void)
+{
+    assert(mReadHandle != kInvalidHandle);
+
+    return mReadHandle;
+}
+
+/*
+ * Get the write handle.
+ */
+unsigned long Pipe::getWriteHandle(void)
+{
+    assert(mWriteHandle != kInvalidHandle);
+
+    return mWriteHandle;
+}
+
diff --git a/simulator/app/Pipe.h b/simulator/app/Pipe.h
new file mode 100644
index 0000000..6404168
--- /dev/null
+++ b/simulator/app/Pipe.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// FIFO I/O.
+//
+#ifndef _LIBS_UTILS_PIPE_H
+#define _LIBS_UTILS_PIPE_H
+
+#ifdef HAVE_ANDROID_OS
+#error DO NOT USE THIS FILE IN THE DEVICE BUILD
+#endif
+
+namespace android {
+
+/*
+ * Simple anonymous unidirectional pipe.
+ *
+ * The primary goal is to create an implementation with minimal overhead
+ * under Linux.  Making Windows, Mac OS X, and Linux all work the same way
+ * is a secondary goal.  Part of this goal is to have something that can
+ * be fed to a select() call, so that the application can sleep in the
+ * kernel until something interesting happens.
+ */
+class Pipe {
+public:
+    Pipe(void);
+    virtual ~Pipe(void);
+
+    /* Create the pipe */
+    bool create(void);
+
+    /* Create a read-only pipe, using the supplied handle as read handle */
+    bool createReader(unsigned long handle);
+    /* Create a write-only pipe, using the supplied handle as write handle */
+    bool createWriter(unsigned long handle);
+
+    /* Is this object ready to go? */
+    bool isCreated(void);
+
+    /*
+     * Read "count" bytes from the pipe.  Returns the amount of data read,
+     * or 0 if no data available and we're non-blocking.
+     * Returns -1 on error.
+     */
+    int read(void* buf, int count);
+
+    /*
+     * Write "count" bytes into the pipe.  Returns number of bytes written,
+     * or 0 if there's no room for more data and we're non-blocking.
+     * Returns -1 on error.
+     */
+    int write(const void* buf, int count);
+
+    /* Returns "true" if data is available to read */
+    bool readReady(void);
+
+    /* Enable or disable non-blocking I/O for reads */
+    bool setReadNonBlocking(bool val);
+    /* Enable or disable non-blocking I/O for writes.  Only works on Linux. */
+    bool setWriteNonBlocking(bool val);
+
+    /*
+     * Get the handle.  Only useful in some platform-specific situations.
+     */
+    unsigned long getReadHandle(void);
+    unsigned long getWriteHandle(void);
+
+    /*
+     * Modify inheritance, i.e. whether or not a child process will get
+     * copies of the descriptors.  Systems with fork+exec allow us to close
+     * the descriptors before launching the child process, but Win32
+     * doesn't allow it.
+     */
+    bool disallowReadInherit(void);
+    bool disallowWriteInherit(void);
+
+    /*
+     * Close one side or the other.  Useful in the parent after launching
+     * a child process.
+     */
+    bool closeRead(void);
+    bool closeWrite(void);
+
+private:
+    bool    mReadNonBlocking;
+    bool    mWriteNonBlocking;
+
+    unsigned long mReadHandle;
+    unsigned long mWriteHandle;
+};
+
+}; // android
+
+#endif // _LIBS_UTILS_PIPE_H
diff --git a/simulator/app/UserEventMessage.h b/simulator/app/UserEventMessage.h
index 4a66cc2..9b94ea7 100644
--- a/simulator/app/UserEventMessage.h
+++ b/simulator/app/UserEventMessage.h
@@ -6,7 +6,7 @@
 #ifndef _SIM_USER_EVENT_MESSAGE_H
 #define _SIM_USER_EVENT_MESSAGE_H
 
-#include <utils.h>
+#include "utils.h"
 #include "LogMessage.h"
 
 /*
diff --git a/simulator/app/executablepath.h b/simulator/app/executablepath.h
new file mode 100644
index 0000000..889982d
--- /dev/null
+++ b/simulator/app/executablepath.h
@@ -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.
+ */
+
+#ifndef _SIM_EXECUTABLEPATH_H
+#define _SIM_EXECUTABLEPATH_H
+
+#include <limits.h>
+
+// returns the path to this executable
+#if __cplusplus
+extern "C"
+#endif
+void executablepath(char s[PATH_MAX]);
+
+#endif // _SIM_EXECUTABLEPATH_H
diff --git a/simulator/app/executablepath_darwin.cpp b/simulator/app/executablepath_darwin.cpp
new file mode 100644
index 0000000..9ec1c18
--- /dev/null
+++ b/simulator/app/executablepath_darwin.cpp
@@ -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.
+ */
+
+#include "executablepath.h"
+#import <Carbon/Carbon.h>
+#include <unistd.h>
+
+void executablepath(char s[PATH_MAX])
+{
+    ProcessSerialNumber psn;
+    GetCurrentProcess(&psn);
+    CFDictionaryRef dict;
+    dict = ProcessInformationCopyDictionary(&psn, 0xffffffff);
+    CFStringRef value = (CFStringRef)CFDictionaryGetValue(dict,
+                CFSTR("CFBundleExecutable"));
+    CFStringGetCString(value, s, PATH_MAX+1, kCFStringEncodingUTF8);
+}
+
diff --git a/simulator/app/executablepath_linux.cpp b/simulator/app/executablepath_linux.cpp
new file mode 100644
index 0000000..e4a3a2b
--- /dev/null
+++ b/simulator/app/executablepath_linux.cpp
@@ -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.
+ */
+
+#include "executablepath.h"
+#include <sys/types.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdio.h>
+
+void executablepath(char exe[PATH_MAX])
+{
+    char proc[100];
+    sprintf(proc, "/proc/%d/exe", getpid());
+    
+    int err = readlink(proc, exe, PATH_MAX);
+}
+
diff --git a/simulator/app/ported.cpp b/simulator/app/ported.cpp
new file mode 100644
index 0000000..232b302
--- /dev/null
+++ b/simulator/app/ported.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Ports of standard functions that don't exist on a specific platform.
+//
+// Note these are NOT in the "android" namespace.
+//
+#include "ported.h"
+
+#if defined(NEED_GETTIMEOFDAY) || defined(NEED_USLEEP)
+# include <sys/time.h>
+# include <windows.h>
+#endif
+
+
+#if defined(NEED_GETTIMEOFDAY)
+/*
+ * Replacement gettimeofday() for Windows environments (primarily MinGW).
+ *
+ * Ignores "tz".
+ */
+int gettimeofday(struct timeval* ptv, struct timezone* tz)
+{
+    long long nsTime;   // time in 100ns units since Jan 1 1601
+    FILETIME ft;
+
+    if (tz != NULL) {
+        // oh well
+    }
+
+    ::GetSystemTimeAsFileTime(&ft);
+    nsTime = (long long) ft.dwHighDateTime << 32 |
+             (long long) ft.dwLowDateTime;
+    // convert to time in usec since Jan 1 1970
+    ptv->tv_usec = (long) ((nsTime / 10LL) % 1000000LL);
+    ptv->tv_sec = (long) ((nsTime - 116444736000000000LL) / 10000000LL);
+
+    return 0;
+}
+#endif
+
+#if defined(NEED_USLEEP)
+//
+// Replacement usleep for Windows environments (primarily MinGW).
+//
+void usleep(unsigned long usec)
+{
+    // Win32 API function Sleep() takes milliseconds
+    ::Sleep((usec + 500) / 1000);
+}
+#endif
+
+#if 0 //defined(NEED_PIPE)
+//
+// Replacement pipe() command for MinGW
+//
+// The _O_NOINHERIT flag sets bInheritHandle to FALSE in the
+// SecurityAttributes argument to CreatePipe().  This means the handles
+// aren't inherited when a new process is created.  The examples I've seen
+// use it, possibly because there's a lot of junk going on behind the
+// scenes.  (I'm assuming "process" and "thread" are different here, so
+// we should be okay spinning up a thread.)  The recommended practice is
+// to dup() the descriptor you want the child to have.
+//
+// It appears that unnamed pipes can't do non-blocking ("overlapped") I/O.
+// You can't use select() either, since that only works on sockets.  The
+// Windows API calls that are useful here all operate on a HANDLE, not
+// an integer file descriptor, and I don't think you can get there from
+// here.  The "named pipe" stuff is insane.
+//
+int pipe(int filedes[2])
+{
+    return _pipe(filedes, 0, _O_BINARY | _O_NOINHERIT);
+}
+#endif
+
+#if defined(NEED_SETENV)
+/*
+ * MinGW lacks these.  For now, just stub them out so the code compiles.
+ */
+int setenv(const char* name, const char* value, int overwrite)
+{
+    return 0;
+}
+void unsetenv(const char* name)
+{
+}
+char* getenv(const char* name)
+{
+    return NULL;
+}
+#endif
diff --git a/simulator/app/ported.h b/simulator/app/ported.h
new file mode 100644
index 0000000..eb3be01
--- /dev/null
+++ b/simulator/app/ported.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Standard functions ported to the current platform.  Note these are NOT
+// in the "android" namespace.
+//
+#ifndef _LIBS_UTILS_PORTED_H
+#define _LIBS_UTILS_PORTED_H
+
+#include <sys/time.h>       // for timeval
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* library replacement functions */
+#if defined(NEED_GETTIMEOFDAY)
+int gettimeofday(struct timeval* tv, struct timezone* tz);
+#endif
+#if defined(NEED_USLEEP)
+void usleep(unsigned long usec);
+#endif
+#if defined(NEED_PIPE)
+int pipe(int filedes[2]);
+#endif
+#if defined(NEED_SETENV)
+int setenv(const char* name, const char* value, int overwrite);
+void unsetenv(const char* name);
+char* getenv(const char* name);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _LIBS_UTILS_PORTED_H
diff --git a/simulator/app/utils.h b/simulator/app/utils.h
new file mode 100644
index 0000000..b74845f
--- /dev/null
+++ b/simulator/app/utils.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Handy utility functions and portability code.  This file includes all
+// of the generally-useful headers in the "utils" directory.
+//
+#ifndef _SIM_UTILS_H
+#define _SIM_UTILS_H
+
+#include "ported.h"
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/List.h>
+#include <utils/StringArray.h>
+#include <utils/misc.h>
+#include <utils/Errors.h>
+
+#endif // _SIM_UTILS_H
diff --git a/simulator/wrapsim/Android.mk b/simulator/wrapsim/Android.mk
index 0b7890d..f9a2414 100644
--- a/simulator/wrapsim/Android.mk
+++ b/simulator/wrapsim/Android.mk
@@ -3,7 +3,6 @@
 #
 # Build instructions for simulator LD_PRELOAD wrapper.
 #
-ifneq ($(TARGET_ARCH),arm)
 ifeq ($(TARGET_SIMULATOR),true)
 
 LOCAL_PATH:= $(call my-dir)
@@ -23,7 +22,8 @@
 	Intercept.c \
 	Log.c \
 	SimMgr.c \
-	SysPower.c
+	SysPower.c \
+	Util.c
 
 LOCAL_C_INCLUDES += prebuilt/common/esd
 
@@ -55,5 +55,3 @@
 include $(BUILD_EXECUTABLE)
 
 endif # ifeq ($(TARGET_SIMULATOR),true)
-endif
-# ifneq ($(TARGET_ARCH),arm)
diff --git a/simulator/wrapsim/Common.h b/simulator/wrapsim/Common.h
index a9c3bb8..463262f 100644
--- a/simulator/wrapsim/Common.h
+++ b/simulator/wrapsim/Common.h
@@ -14,5 +14,6 @@
 #include "Log.h"
 #include "SimMgr.h"
 #include "Globals.h"
+#include "Util.h"
 
 #endif /*_WRAPSIM_COMMON_H*/
diff --git a/simulator/wrapsim/DevEvent.c b/simulator/wrapsim/DevEvent.c
index 692856e..60060f4 100644
--- a/simulator/wrapsim/DevEvent.c
+++ b/simulator/wrapsim/DevEvent.c
@@ -31,14 +31,34 @@
  * (For now, just pretend to be a "goldfish" like the emulator.)
  */
 static const unsigned char gKeyBitMask[64] = {
+    // These bits indicate which keys the device has
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+    // These bits indicate other capabilities, such
+    // as whether it's a trackball or a touchscreen
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // touchscreen
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * Abs bit mask, for EVIOCGBIT(EV_ABS).
+ *
+ * Pretend to be a normal single touch panel
+ */
+static const unsigned char gAbsBitMask[64] = {
+    // these bits indicate the capabilities of the touch screen
+    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ABS_X, ABS_Y
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 };
 
 /*
@@ -193,7 +213,9 @@
     } else if (!getenv("NOTOUCH") && _IOC_NR(urequest) == _IOC_NR(EVIOCGBIT(EV_ABS,0))) {
         // absolute controllers (touch screen)
         int maxLen = _IOC_SIZE(urequest);
-        memset(argp, 0xff, maxLen);
+        if (maxLen > (int) sizeof(gAbsBitMask))
+            maxLen = sizeof(gAbsBitMask);
+        memcpy(argp, gAbsBitMask, maxLen);
 
     } else if (_IOC_NR(urequest) >= _IOC_NR(EVIOCGABS(ABS_X)) &&
                _IOC_NR(urequest) <= _IOC_NR(EVIOCGABS(ABS_MAX)))
diff --git a/simulator/wrapsim/DevFb.c b/simulator/wrapsim/DevFb.c
index c54403e..bfdbb22 100644
--- a/simulator/wrapsim/DevFb.c
+++ b/simulator/wrapsim/DevFb.c
@@ -14,6 +14,10 @@
 #include <linux/fb.h>
 
 typedef struct FbState {
+
+    /* refcount for dup() */
+    int refCount;
+
     /* index into gWrapSim.display[] */
     int     displayIdx;
 
@@ -77,7 +81,13 @@
  */
 static void freeState(FbState* fbState)
 {
-    free(fbState);
+    int oldcount;
+
+    oldcount = wsAtomicAdd(&fbState->refCount, -1);
+
+    if (oldcount == 0) {
+        free(fbState);
+    }
 }
 
 /*
@@ -242,6 +252,28 @@
 }
 
 /*
+ * dup() an existing fake descriptor
+ */
+static FakeDev* dupFb(FakeDev* dev, int fd)
+{
+    FakeDev* newDev = wsCreateFakeDev(dev->debugName);
+    if (newDev != NULL) {
+        newDev->mmap = mmapFb;
+        newDev->ioctl = ioctlFb;
+        newDev->close = closeFb;
+        newDev->dup = dupFb;
+
+        /* use state from existing FakeDev */
+        FbState* fbState = dev->state;
+        wsAtomicAdd(&fbState->refCount, 1);
+
+        newDev->state = fbState;
+    }
+
+    return newDev;
+}
+
+/*
  * Open the console TTY device, which responds to a collection of ioctl()s.
  */
 FakeDev* wsOpenDevFb(const char* pathName, int flags)
@@ -251,6 +283,7 @@
         newDev->mmap = mmapFb;
         newDev->ioctl = ioctlFb;
         newDev->close = closeFb;
+        newDev->dup = dupFb;
 
         FbState* fbState = calloc(1, sizeof(FbState));
 
diff --git a/simulator/wrapsim/FakeDev.c b/simulator/wrapsim/FakeDev.c
index 7d2494e..f03dd29 100644
--- a/simulator/wrapsim/FakeDev.c
+++ b/simulator/wrapsim/FakeDev.c
@@ -99,6 +99,11 @@
 {
     return 0;
 }
+static FakeDev* noDup(FakeDev* dev, ...)
+{
+    notImplemented(dev, "dup");
+    return NULL;
+}
 static int noRead(FakeDev* dev, ...)
 {
     return notImplemented(dev, "read");
@@ -146,6 +151,7 @@
     newDev->state = NULL;
 
     newDev->close = (Fake_close) noClose;
+    newDev->dup = (Fake_dup) noDup;
     newDev->read = (Fake_read) noRead;
     newDev->readv = (Fake_readv) noReadv;
     newDev->write = (Fake_write) noWrite;
diff --git a/simulator/wrapsim/FakeDev.h b/simulator/wrapsim/FakeDev.h
index 4781cfc..65f47ae 100644
--- a/simulator/wrapsim/FakeDev.h
+++ b/simulator/wrapsim/FakeDev.h
@@ -12,13 +12,14 @@
 
 typedef struct FakeDev FakeDev;
 
-typedef int     (*Fake_close)(FakeDev* dev, int);
-typedef ssize_t (*Fake_read)(FakeDev* dev, int, void*, size_t);
-typedef ssize_t (*Fake_readv)(FakeDev* dev, int, const struct iovec*, int);
-typedef ssize_t (*Fake_write)(FakeDev* dev, int, const void*, size_t);
-typedef ssize_t (*Fake_writev)(FakeDev* dev, int, const struct iovec*, int);
-typedef void*   (*Fake_mmap)(FakeDev* dev, void*, size_t, int, int, int, __off_t);
-typedef int     (*Fake_ioctl)(FakeDev* dev, int, int, void*);
+typedef int      (*Fake_close)(FakeDev* dev, int);
+typedef FakeDev* (*Fake_dup)(FakeDev* dev, int);
+typedef ssize_t  (*Fake_read)(FakeDev* dev, int, void*, size_t);
+typedef ssize_t  (*Fake_readv)(FakeDev* dev, int, const struct iovec*, int);
+typedef ssize_t  (*Fake_write)(FakeDev* dev, int, const void*, size_t);
+typedef ssize_t  (*Fake_writev)(FakeDev* dev, int, const struct iovec*, int);
+typedef void*    (*Fake_mmap)(FakeDev* dev, void*, size_t, int, int, int, __off_t);
+typedef int      (*Fake_ioctl)(FakeDev* dev, int, int, void*);
 
 /*
  * An open fake device entry.
@@ -42,6 +43,7 @@
      * All other file descriptor operations should fail, usually with EBADF.
      */
     Fake_close  close;
+    Fake_dup  dup;
     Fake_read   read;
     Fake_readv  readv;
     Fake_write  write;
diff --git a/simulator/wrapsim/Globals.h b/simulator/wrapsim/Globals.h
index 75c98d8..a8d834c 100644
--- a/simulator/wrapsim/Globals.h
+++ b/simulator/wrapsim/Globals.h
@@ -29,6 +29,7 @@
 typedef int     (*Func_open64)(const char*, int, mode_t);
 
 typedef int     (*Func_close)(int);
+typedef int     (*Func_dup)(int);
 typedef ssize_t (*Func_read)(int, void*, size_t);
 typedef ssize_t (*Func_readv)(int, const struct iovec*, int);
 typedef ssize_t (*Func_write)(int, const void*, size_t);
@@ -95,6 +96,7 @@
 EXTERN_FUNC Func_open64 _ws_open64;
 
 EXTERN_FUNC Func_close _ws_close;
+EXTERN_FUNC Func_dup _ws_dup;
 EXTERN_FUNC Func_read _ws_read;
 EXTERN_FUNC Func_readv _ws_readv;
 EXTERN_FUNC Func_write _ws_write;
@@ -201,6 +203,9 @@
     pthread_mutex_t fakeFdLock;
     BitVector*  fakeFdMap;
     FakeDev*    fakeFdList[kMaxFakeFdCount];
+
+    /* used for wsAtomicAdd */
+    pthread_mutex_t atomicLock;
 };
 
 extern struct WrapSimGlobals gWrapSim;
diff --git a/simulator/wrapsim/Init.c b/simulator/wrapsim/Init.c
index eed650b..3df0efe 100644
--- a/simulator/wrapsim/Init.c
+++ b/simulator/wrapsim/Init.c
@@ -40,6 +40,7 @@
     _ws_open64 = dlsym(RTLD_NEXT, "open64");
 
     _ws_close = dlsym(RTLD_NEXT, "close");
+    _ws_dup = dlsym(RTLD_NEXT, "dup");
     _ws_read = dlsym(RTLD_NEXT, "read");
     _ws_readv = dlsym(RTLD_NEXT, "readv");
     _ws_write = dlsym(RTLD_NEXT, "write");
@@ -108,6 +109,8 @@
     gWrapSim.fakeFdMap = wsAllocBitVector(kMaxFakeFdCount, 0);
     memset(gWrapSim.fakeFdList, 0, sizeof(gWrapSim.fakeFdList));
 
+    pthread_mutex_init(&gWrapSim.atomicLock, NULL);
+
     gWrapSim.numDisplays = 0;
 
     gWrapSim.keyInputDevice = NULL;
diff --git a/simulator/wrapsim/Intercept.c b/simulator/wrapsim/Intercept.c
index 49d77ee..3d4edb2 100644
--- a/simulator/wrapsim/Intercept.c
+++ b/simulator/wrapsim/Intercept.c
@@ -160,7 +160,7 @@
     _rtype _fname( __VA_ARGS__ )
 #define PASS_THROUGH_BODY(_fname, _patharg, ...)                            \
     {                                                                       \
-        CALLTRACEV("%s\n", __FUNCTION__);                                   \
+        CALLTRACEV("%s(%s)\n", __FUNCTION__, _patharg);                     \
         char pathBuf[PATH_MAX];                                             \
         return _ws_##_fname(rewritePath(#_fname, pathBuf, _patharg),        \
             ##__VA_ARGS__);                                                 \
@@ -631,6 +631,30 @@
 }
 
 
+int dup(int fd)
+{
+    CALLTRACEV("%s(%d)\n", __FUNCTION__, fd);
+
+    FakeDev* dev = wsFakeDevFromFd(fd);
+    if (dev != NULL) {
+        FakeDev* newDev = dev->dup(dev, fd);
+        if (newDev != NULL) {
+            /*
+             * Now that the device entry is ready, add it to the list.
+             */
+            wsLog("## dup'ed fake dev %d: '%s' %p\n",
+                newDev->fd, newDev->debugName, newDev->state);
+            gWrapSim.fakeFdList[newDev->fd - kFakeFdBase] = newDev;
+            return newDev->fd;
+        }
+        return -1;
+    } else {
+        CALLTRACE("dup(%d)\n", fd);
+        return _ws_dup(fd);
+    }
+}
+
+
 /*
  * Close a file descriptor.
  */
@@ -799,6 +823,16 @@
     return 0;
 }
 
+/*
+ * Pretend to be running as root, so the Android framework
+ * doesn't complain about permission problems all over the
+ * place.
+ */
+uid_t getuid(void)
+{
+    return 0;
+}
+
 #if 0
 /*
  * Create a pipe.  (Only needed for debugging an fd leak.)
diff --git a/simulator/wrapsim/Util.c b/simulator/wrapsim/Util.c
new file mode 100644
index 0000000..33d903b
--- /dev/null
+++ b/simulator/wrapsim/Util.c
@@ -0,0 +1,13 @@
+
+#include "Common.h"
+
+int wsAtomicAdd(int *var, int val)
+{
+    int cc;
+    int ret;
+    cc = pthread_mutex_lock(&gWrapSim.atomicLock);
+    ret = *var;
+    *var = *var + val;
+    cc = pthread_mutex_unlock(&gWrapSim.atomicLock);
+    return ret;
+}
diff --git a/simulator/wrapsim/Util.h b/simulator/wrapsim/Util.h
new file mode 100644
index 0000000..e470802
--- /dev/null
+++ b/simulator/wrapsim/Util.h
@@ -0,0 +1,4 @@
+
+
+
+int wsAtomicAdd(int *var, int val);
diff --git a/testrunner/am_instrument_parser.py b/testrunner/am_instrument_parser.py
index cad87c0..4554c4d 100755
--- a/testrunner/am_instrument_parser.py
+++ b/testrunner/am_instrument_parser.py
@@ -80,7 +80,7 @@
     code.
   """
 
-  re_result = re.compile(r'INSTRUMENTATION_RESULT: ([^=]+)=(.+)$')
+  re_result = re.compile(r'INSTRUMENTATION_RESULT: ([^=]+)=(.*)$')
   re_code = re.compile(r'INSTRUMENTATION_CODE: (\-?\d)$')
   result_dict = {}
   key = ''
@@ -135,38 +135,26 @@
     self._test_name = None
     self._status_code = None
     self._failure_reason = None
+    self._fields_map = {}
 
-    re_start_block = re.compile(
-       r'\s*INSTRUMENTATION_STATUS: stream=(?P<stream>.*)'
-        'INSTRUMENTATION_STATUS: test=(?P<test>\w+)\s+'
-        'INSTRUMENTATION_STATUS: class=(?P<class>[\w\.]+)\s+'
-        'INSTRUMENTATION_STATUS: current=(?P<current>\d+)\s+'
-        'INSTRUMENTATION_STATUS: numtests=(?P<numtests>\d+)\s+'
-        'INSTRUMENTATION_STATUS: id=.*\s+'
-        'INSTRUMENTATION_STATUS_CODE: 1\s*', re.DOTALL)
+    re_status_code = re.search(r'INSTRUMENTATION_STATUS_CODE: '
+        '(?P<status_code>1|0|-1|-2)', result_block_string)
+    re_fields = re.compile(r'INSTRUMENTATION_STATUS: '
+        '(?P<key>[\w.]+)=(?P<value>.*?)(?=\nINSTRUMENTATION_STATUS)', re.DOTALL)
 
-    re_end_block = re.compile(
-       r'\s*INSTRUMENTATION_STATUS: stream=(?P<stream>.*)'
-        'INSTRUMENTATION_STATUS: test=(?P<test>\w+)\s+'
-        '(INSTRUMENTATION_STATUS: stack=(?P<stack>.*))?'
-        'INSTRUMENTATION_STATUS: class=(?P<class>[\w\.]+)\s+'
-        'INSTRUMENTATION_STATUS: current=(?P<current>\d+)\s+'
-        'INSTRUMENTATION_STATUS: numtests=(?P<numtests>\d+)\s+'
-        'INSTRUMENTATION_STATUS: id=.*\s+'
-        'INSTRUMENTATION_STATUS_CODE: (?P<status_code>0|-1|-2)\s*', re.DOTALL)
+    for field in re_fields.finditer(result_block_string):
+      key, value = (field.group('key').strip(), field.group('value').strip())
+      if key.startswith('performance.'):
+        key = key[len('performance.'):]
+      self._fields_map[key] = value
+    self._fields_map.setdefault('class')
+    self._fields_map.setdefault('test')
 
-    start_block_match = re_start_block.match(result_block_string)
-    end_block_match = re_end_block.match(result_block_string)
-
-    if start_block_match:
-      self._test_name = "%s:%s" % (start_block_match.group('class'),
-                                   start_block_match.group('test'))
-      self._status_code = 1
-    elif end_block_match:
-      self._test_name = "%s:%s" % (end_block_match.group('class'),
-                                   end_block_match.group('test'))
-      self._status_code = int(end_block_match.group('status_code'))
-      self._failure_reason = end_block_match.group('stack')
+    self._test_name = '%s:%s' % (self._fields_map['class'],
+                                 self._fields_map['test'])
+    self._status_code = int(re_status_code.group('status_code'))
+    if 'stack' in self._fields_map:
+      self._failure_reason = self._fields_map['stack']
 
   def GetTestName(self):
     return self._test_name
@@ -176,3 +164,6 @@
 
   def GetFailureReason(self):
     return self._failure_reason
+
+  def GetResultFields(self):
+    return self._fields_map
diff --git a/testrunner/run_command.py b/testrunner/run_command.py
index 7d1f547..79c7ea5 100755
--- a/testrunner/run_command.py
+++ b/testrunner/run_command.py
@@ -144,7 +144,7 @@
   else:
     # Need the full path to valgrind to avoid other versions on the system.
     subproc = subprocess.Popen(["/usr/bin/valgrind", "--tool=memcheck",
-                                "--leak-check=yes", "-q", full_path],
+                                "--leak-check=yes", "-q", binary],
                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     # Cannot rely on the retcode of valgrind. Instead look for an empty output.
     valgrind_out = subproc.communicate()[0].strip()
diff --git a/testrunner/test_defs.xml b/testrunner/test_defs.xml
index 48a6786..56cab33 100644
--- a/testrunner/test_defs.xml
+++ b/testrunner/test_defs.xml
@@ -44,6 +44,12 @@
     coverage_target="framework"
     continuous="true" />
 
+<test name="account"
+    build_path="frameworks/base/tests/AndroidTests"
+    package="com.android.unit_tests"
+    class="com.android.unit_tests.accounts.AccountManagerServiceTest"
+    coverage_target="framework" />
+
 <test name="smoke"
     build_path="frameworks/base/tests/SmokeTest"
     package="com.android.smoketest.tests"
@@ -103,6 +109,12 @@
  -->
 
 
+<test name="contentprovideroperation"
+    build_path="frameworks/base/tests/FrameworkTest"
+    package="com.android.frameworktest.tests"
+    class="android.content.ContentProviderOperationTest"
+    coverage_target="framework" />
+
 <test name="tablemerger"
     build_path="frameworks/base/tests/FrameworkTest"
     package="com.android.frameworktest.tests"
@@ -434,7 +446,6 @@
 <test name="mms"
     build_path="packages/apps/Mms"
     package="com.android.mms.tests"
-    runner="com.android.mms.ui.MMSInstrumentationTestRunner"
     coverage_target="Mms" />
 
 <test name="mmslaunch"
@@ -460,6 +471,10 @@
     description="Bionic libstdc++."
     extra_build_args="BIONIC_TESTS=1" />
 
+<test-native name="libskia"
+    build_path="external/skia/tests"
+    description="Skia tests." />
+
 <!--  Android STL tests -->
 <test-native name="astl"
     build_path="external/astl/tests"
diff --git a/testrunner/tests/am_instrument_parser_tests.py b/testrunner/tests/am_instrument_parser_tests.py
new file mode 100755
index 0000000..55356eb
--- /dev/null
+++ b/testrunner/tests/am_instrument_parser_tests.py
@@ -0,0 +1,201 @@
+#!/usr/bin/python2.4
+#
+#
+# Copyright 2009, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import sys
+import unittest
+sys.path.append('../..')
+
+from testrunner import am_instrument_parser
+
+
+class AmParserTest(unittest.TestCase):
+
+  def testParseAmInstResult(self):
+    result="""INSTRUMENTATION_RESULT: performance.java_size=4871
+INSTRUMENTATION_RESULT: stream=
+Error: Failed to generate emma coverage.
+INSTRUMENTATION_RESULT: performance.cpu_time=33846
+INSTRUMENTATION_CODE: -1
+"""
+    bundle_dict = \
+        am_instrument_parser._ParseInstrumentationFinishedBundle(result)
+    self.assertEquals(4871, bundle_dict['java_size'])
+    self.assertEquals(33846, bundle_dict['cpu_time'])
+    self.assertEquals("\nError: Failed to generate emma coverage.",
+        bundle_dict['stream'])
+
+  def testParseAmInstStatus(self):
+    # numtests before id
+    segment1 = """INSTRUMENTATION_STATUS: stream=
+INSTRUMENTATION_STATUS: test=testLaunchComplexActivity
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTest
+INSTRUMENTATION_STATUS: current=1
+INSTRUMENTATION_STATUS: numtests=2
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS_CODE: 1"""
+    segment2 = """INSTRUMENTATION_STATUS: stream=.
+INSTRUMENTATION_STATUS: test=testLaunchComplexActivity
+INSTRUMENTATION_STATUS: performance.cpu_time=866
+INSTRUMENTATION_STATUS: performance.execution_time=1242
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTest
+INSTRUMENTATION_STATUS: current=1
+INSTRUMENTATION_STATUS: numtests=2
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS_CODE: 0"""
+    # numtests after id
+    segment3 = """INSTRUMENTATION_STATUS: stream=
+INSTRUMENTATION_STATUS: test=testLaunchSimpleActivity
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTest
+INSTRUMENTATION_STATUS: current=2
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS: numtests=8
+INSTRUMENTATION_STATUS_CODE: 1"""
+    segment4 = """INSTRUMENTATION_STATUS: stream=.
+INSTRUMENTATION_STATUS: test=testLaunchSimpleActivity
+INSTRUMENTATION_STATUS: performance.cpu_time=590
+INSTRUMENTATION_STATUS: performance.execution_time=1122
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTest
+INSTRUMENTATION_STATUS: current=2
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS: numtests=8
+INSTRUMENTATION_STATUS_CODE: 0"""
+
+    result = am_instrument_parser.TestResult(segment1)
+    map = result.GetResultFields()
+    self.assertEquals('testLaunchComplexActivity', map['test'])
+    self.assertEquals('LaunchPerformanceTest', map['class'])
+    self.assertEquals('1', map['current'])
+    self.assertEquals('2', map['numtests'])
+    self.assertEquals('InstrumentationTestRunner', map['id'])
+    self.assertEquals(1, result.GetStatusCode())
+
+    result = am_instrument_parser.TestResult(segment2)
+    map = result.GetResultFields()
+    self.assertEquals('testLaunchComplexActivity', map['test'])
+    self.assertEquals('866', map['cpu_time'])
+    self.assertEquals('1242', map['execution_time'])
+    self.assertEquals('LaunchPerformanceTest', map['class'])
+    self.assertEquals('1', map['current'])
+    self.assertEquals('2', map['numtests'])
+    self.assertEquals('InstrumentationTestRunner', map['id'])
+    self.assertEquals(0, result.GetStatusCode())
+
+    result = am_instrument_parser.TestResult(segment3)
+    map = result.GetResultFields()
+    self.assertEquals('testLaunchSimpleActivity', map['test'])
+    self.assertEquals('LaunchPerformanceTest', map['class'])
+    self.assertEquals('2', map['current'])
+    self.assertEquals('8', map['numtests'])
+    self.assertEquals('InstrumentationTestRunner', map['id'])
+    self.assertEquals(1, result.GetStatusCode())
+
+    result = am_instrument_parser.TestResult(segment4)
+    map = result.GetResultFields()
+    self.assertEquals('testLaunchSimpleActivity', map['test'])
+    self.assertEquals('590', map['cpu_time'])
+    self.assertEquals('1122', map['execution_time'])
+    self.assertEquals('LaunchPerformanceTest', map['class'])
+    self.assertEquals('2', map['current'])
+    self.assertEquals('8', map['numtests'])
+    self.assertEquals('InstrumentationTestRunner', map['id'])
+    self.assertEquals(0, result.GetStatusCode())
+
+  def testParseAmInstOutput(self):
+    result = """INSTRUMENTATION_STATUS: class=LaunchPerformanceTestCase
+INSTRUMENTATION_STATUS: current=1
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS: numtests=2
+INSTRUMENTATION_STATUS: stream=
+LaunchPerformanceTestCase:
+INSTRUMENTATION_STATUS: test=testLaunchComplexActivity
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTestCase
+INSTRUMENTATION_STATUS: current=1
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS: numtests=2
+INSTRUMENTATION_STATUS: performance.cpu_time=866
+INSTRUMENTATION_STATUS: performance.execution_time=1242
+INSTRUMENTATION_STATUS: stream=.
+INSTRUMENTATION_STATUS: test=testLaunchComplexActivity
+INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTestCase
+INSTRUMENTATION_STATUS: current=2
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS: numtests=2
+INSTRUMENTATION_STATUS: stream=
+INSTRUMENTATION_STATUS: test=testLaunchSimpleActivity
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: class=LaunchPerformanceTestCase
+INSTRUMENTATION_STATUS: current=2
+INSTRUMENTATION_STATUS: id=InstrumentationTestRunner
+INSTRUMENTATION_STATUS: numtests=2
+INSTRUMENTATION_STATUS: performance.cpu_time=590
+INSTRUMENTATION_STATUS: performance.execution_time=1122
+INSTRUMENTATION_STATUS: stream=.
+INSTRUMENTATION_STATUS: test=testLaunchSimpleActivity
+INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_RESULT: performance.cpu_time=829
+INSTRUMENTATION_RESULT: performance.execution_time=1708
+INSTRUMENTATION_RESULT: performance.gc_invocation_count=0
+INSTRUMENTATION_RESULT: performance.global_alloc_count=2848
+INSTRUMENTATION_RESULT: performance.global_alloc_size=193079
+INSTRUMENTATION_RESULT: performance.global_freed_count=1207
+INSTRUMENTATION_RESULT: performance.global_freed_size=93040
+INSTRUMENTATION_RESULT: performance.java_allocated=2175
+INSTRUMENTATION_RESULT: performance.java_free=580
+INSTRUMENTATION_RESULT: performance.java_private_dirty=740
+INSTRUMENTATION_RESULT: performance.java_pss=1609
+INSTRUMENTATION_RESULT: performance.java_shared_dirty=3860
+INSTRUMENTATION_RESULT: performance.java_size=2755
+INSTRUMENTATION_RESULT: performance.native_allocated=2585
+INSTRUMENTATION_RESULT: performance.native_free=34
+INSTRUMENTATION_RESULT: performance.native_private_dirty=632
+INSTRUMENTATION_RESULT: performance.native_pss=701
+INSTRUMENTATION_RESULT: performance.native_shared_dirty=1164
+INSTRUMENTATION_RESULT: performance.native_size=2620
+INSTRUMENTATION_RESULT: performance.other_private_dirty=896
+INSTRUMENTATION_RESULT: performance.other_pss=1226
+INSTRUMENTATION_RESULT: performance.other_shared_dirty=804
+INSTRUMENTATION_RESULT: performance.pre_received_transactions=-1
+INSTRUMENTATION_RESULT: performance.pre_sent_transactions=-1
+INSTRUMENTATION_RESULT: performance.received_transactions=-1
+INSTRUMENTATION_RESULT: performance.sent_transactions=-1
+INSTRUMENTATION_RESULT: stream=
+Test results for InstrumentationTestRunner=..
+Time: 2.413
+
+OK (2 tests)
+
+
+INSTRUMENTATION_CODE: -1
+"""
+    (results_list, perf_dict) = \
+        am_instrument_parser.ParseAmInstrumentOutput(result)
+    self.assertEquals(829, perf_dict['cpu_time'])
+    self.assertEquals(2848, perf_dict['global_alloc_count'])
+    self.assertEquals(93040, perf_dict['global_freed_size'])
+    self.assertEquals(740, perf_dict['java_private_dirty'])
+    self.assertEquals(2755, perf_dict['java_size'])
+    self.assertEquals(632, perf_dict['native_private_dirty'])
+    self.assertEquals(2620, perf_dict['native_size'])
+    self.assertEquals(804, perf_dict['other_shared_dirty'])
+    self.assertEquals(-1, perf_dict['received_transactions'])
+    self.assertTrue(len(perf_dict['stream']) > 50)
+    self.assertEquals('-1', perf_dict['code'])
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
index e8e8103..0789655 100644
--- a/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
+++ b/tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
@@ -73,11 +73,14 @@
     /**
      * Send a MPRS (Method PRofiling Start) request to the client.
      *
+     * The arguments to this method will eventually be passed to
+     * android.os.Debug.startMethodTracing() on the device.
+     *
      * @param fileName is the name of the file to which profiling data
      *          will be written (on the device); it will have ".trace"
      *          appended if necessary
      * @param bufferSize is the desired buffer size in bytes (8MB is good)
-     * @param flags should be zero
+     * @param flags see startMethodTracing() docs; use 0 for default behavior
      */
     public static void sendMPRS(Client client, String fileName, int bufferSize,
         int flags) throws IOException {
diff --git a/tools/eclipse/features/com.android.ide.eclipse.adt/feature.xml b/tools/eclipse/features/com.android.ide.eclipse.adt/feature.xml
index 6dd3ee7..e4cc9f2 100644
--- a/tools/eclipse/features/com.android.ide.eclipse.adt/feature.xml
+++ b/tools/eclipse/features/com.android.ide.eclipse.adt/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="com.android.ide.eclipse.adt"
       label="Android Development Tools"
-      version="0.9.3.qualifier"
+      version="0.9.4.qualifier"
       provider-name="The Android Open Source Project"
       plugin="com.android.ide.eclipse.adt">
 
diff --git a/tools/eclipse/features/com.android.ide.eclipse.ddms/feature.xml b/tools/eclipse/features/com.android.ide.eclipse.ddms/feature.xml
index e67f960..c0cb12d 100644
--- a/tools/eclipse/features/com.android.ide.eclipse.ddms/feature.xml
+++ b/tools/eclipse/features/com.android.ide.eclipse.ddms/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="com.android.ide.eclipse.ddms"
       label="Android DDMS"
-      version="0.9.3.qualifier"
+      version="0.9.4.qualifier"
       provider-name="The Android Open Source Project">
 
    <description>
diff --git a/tools/eclipse/features/com.android.ide.eclipse.tests/feature.xml b/tools/eclipse/features/com.android.ide.eclipse.tests/feature.xml
index cc34045..679da31 100644
--- a/tools/eclipse/features/com.android.ide.eclipse.tests/feature.xml
+++ b/tools/eclipse/features/com.android.ide.eclipse.tests/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="com.android.ide.eclipse.tests"
       label="ADT Tests"
-      version="0.9.3.qualifier"
+      version="0.9.4.qualifier"
       provider-name="The Android Open Source Project">
 
    <copyright>
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.adt/.gitignore
new file mode 100644
index 0000000..d392f0e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 32c939b..e840e59 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: Android Development Toolkit
 Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
-Bundle-Version: 0.9.3.qualifier
+Bundle-Version: 0.9.4.qualifier
 Bundle-ClassPath: .,
  jarutils.jar,
  androidprefs.jar,
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
index 225d5bd..0dfa0b4 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/GraphicalLayoutEditor.java
@@ -20,6 +20,8 @@
 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor.UiEditorActions;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener;
 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
 import com.android.ide.eclipse.adt.internal.editors.layout.parts.ElementCreateCommand;
 import com.android.ide.eclipse.adt.internal.editors.layout.parts.UiElementEditPart;
@@ -28,25 +30,9 @@
 import com.android.ide.eclipse.adt.internal.editors.ui.tree.PasteAction;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.ide.eclipse.adt.internal.resources.ResourceType;
-import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier;
 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
-import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier;
 import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier;
-import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier.KeyboardState;
-import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier.NavigationMethod;
 import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density;
-import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier.ScreenOrientation;
-import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier.TextInputMethod;
-import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier.TouchScreenType;
 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile;
 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
@@ -56,14 +42,10 @@
 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge;
 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.DimensionVerifier;
-import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.LanguageRegionVerifier;
-import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.MobileCodeVerifier;
 import com.android.layoutlib.api.ILayoutLog;
 import com.android.layoutlib.api.ILayoutResult;
 import com.android.layoutlib.api.IProjectCallback;
 import com.android.layoutlib.api.IResourceValue;
-import com.android.layoutlib.api.IStyleResourceValue;
 import com.android.layoutlib.api.IXmlPullParser;
 import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
 import com.android.sdklib.IAndroidTarget;
@@ -97,22 +79,12 @@
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.dnd.Clipboard;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.ImageData;
 import org.eclipse.swt.graphics.PaletteData;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Combo;
 import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.IEditorInput;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.ide.IDE;
@@ -127,12 +99,9 @@
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Graphical layout editor, based on GEF.
@@ -142,9 +111,8 @@
  * To understand Drag'n'drop: http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
  */
 public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
-        implements ILayoutReloadListener {
+        implements ILayoutReloadListener, IConfigListener {
 
-    private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
 
     /** Reference to the layout editor */
     private final LayoutEditor mLayoutEditor;
@@ -154,43 +122,10 @@
 
     private Clipboard mClipboard;
     private Composite mParent;
+    private ConfigurationComposite mConfigComposite;
+
     private PaletteRoot mPaletteRoot;
 
-    private Text mCountry;
-    private Text mNetwork;
-    private Combo mLanguage;
-    private Combo mRegion;
-    private Combo mOrientation;
-    private Combo mDensity;
-    private Combo mTouch;
-    private Combo mKeyboard;
-    private Combo mTextInput;
-    private Combo mNavigation;
-    private Text mSize1;
-    private Text mSize2;
-    private Combo mThemeCombo;
-    private Button mCreateButton;
-
-    private Label mCountryIcon;
-    private Label mNetworkIcon;
-    private Label mLanguageIcon;
-    private Label mRegionIcon;
-    private Label mOrientationIcon;
-    private Label mDensityIcon;
-    private Label mTouchIcon;
-    private Label mKeyboardIcon;
-    private Label mTextInputIcon;
-    private Label mNavigationIcon;
-    private Label mSizeIcon;
-
-    private Label mCurrentLayoutLabel;
-
-    private Image mWarningImage;
-    private Image mMatchImage;
-    private Image mErrorImage;
-
-    /** The {@link FolderConfiguration} representing the state of the UI controls */
-    private FolderConfiguration mCurrentConfig = new FolderConfiguration();
     /** The {@link FolderConfiguration} being edited. */
     private FolderConfiguration mEditedConfig;
 
@@ -201,8 +136,6 @@
 
     private boolean mNeedsXmlReload = false;
     private boolean mNeedsRecompute = false;
-    private int mPlatformThemeCount = 0;
-    private boolean mDisableUpdates = false;
 
     /** Listener to update the root node if the target of the file is changed because of a
      * SDK location change or a project target change */
@@ -217,9 +150,7 @@
             // because the SDK changed we must reset the configured framework resource.
             mConfiguredFrameworkRes = null;
 
-            updateUIFromResources();
-
-            mThemeCombo.getParent().layout();
+            mConfigComposite.updateUIFromResources();
 
             // updateUiFromFramework will reset language/region combo, so we must call
             // setConfiguration after, or the settext on language/region will be lost.
@@ -247,21 +178,16 @@
 
     private final Runnable mUiUpdateFromResourcesRunnable = new Runnable() {
         public void run() {
-            updateUIFromResources();
-            mThemeCombo.getParent().layout();
+            mConfigComposite.updateUIFromResources();
         }
     };
 
+
     public GraphicalLayoutEditor(LayoutEditor layoutEditor) {
         mLayoutEditor = layoutEditor;
         setEditDomain(new DefaultEditDomain(this));
         setPartName("Layout");
 
-        IconFactory factory = IconFactory.getInstance();
-        mWarningImage = factory.getIcon("warning"); //$NON-NLS-1$
-        mMatchImage = factory.getIcon("match"); //$NON-NLS-1$
-        mErrorImage = factory.getIcon("error"); //$NON-NLS-1$
-
         AdtPlugin.getDefault().addTargetListener(mTargetListener);
     }
 
@@ -273,7 +199,6 @@
     public void createPartControl(Composite parent) {
         mParent = parent;
         GridLayout gl;
-        GridData gd;
 
         mClipboard = new Clipboard(parent.getDisplay());
 
@@ -281,286 +206,7 @@
         gl.marginHeight = gl.marginWidth = 0;
 
         // create the top part for the configuration control
-        int cols = 10;
-
-        Composite topParent = new Composite(parent, SWT.NONE);
-        topParent.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
-        topParent.setLayout(gl = new GridLayout(cols, false));
-
-        new Label(topParent, SWT.NONE).setText("MCC");
-        mCountryIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mCountry = new Text(mCountryIcon.getParent(), SWT.BORDER);
-        mCountry.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mCountry.addVerifyListener(new MobileCodeVerifier());
-        mCountry.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetDefaultSelected(SelectionEvent e) {
-                onCountryCodeChange();
-            }
-        });
-        mCountry.addModifyListener(new ModifyListener() {
-            public void modifyText(ModifyEvent e) {
-                onCountryCodeChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("MNC");
-        mNetworkIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mNetwork = new Text(mNetworkIcon.getParent(), SWT.BORDER);
-        mNetwork.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mNetwork.addVerifyListener(new MobileCodeVerifier());
-        mNetwork.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetDefaultSelected(SelectionEvent e) {
-                onNetworkCodeChange();
-            }
-        });
-        mNetwork.addModifyListener(new ModifyListener() {
-            public void modifyText(ModifyEvent e) {
-                onNetworkCodeChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Lang");
-        mLanguageIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mLanguage = new Combo(mLanguageIcon.getParent(), SWT.DROP_DOWN);
-        mLanguage.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mLanguage.addVerifyListener(new LanguageRegionVerifier());
-        mLanguage.addSelectionListener(new SelectionListener() {
-            public void widgetDefaultSelected(SelectionEvent e) {
-                onLanguageChange();
-            }
-            public void widgetSelected(SelectionEvent e) {
-                onLanguageChange();
-            }
-        });
-        mLanguage.addModifyListener(new ModifyListener() {
-            public void modifyText(ModifyEvent e) {
-                onLanguageChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Region");
-        mRegionIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mRegion = new Combo(mRegionIcon.getParent(), SWT.DROP_DOWN);
-        mRegion.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mRegion.addVerifyListener(new LanguageRegionVerifier());
-        mRegion.addSelectionListener(new SelectionListener() {
-            public void widgetDefaultSelected(SelectionEvent e) {
-                onRegionChange();
-            }
-            public void widgetSelected(SelectionEvent e) {
-                onRegionChange();
-            }
-        });
-        mRegion.addModifyListener(new ModifyListener() {
-            public void modifyText(ModifyEvent e) {
-                onRegionChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Orient");
-        mOrientationIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mOrientation = new Combo(mOrientationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
-        ScreenOrientation[] soValues = ScreenOrientation.values();
-        mOrientation.add("(Default)");
-        for (ScreenOrientation value : soValues) {
-            mOrientation.add(value.getDisplayValue());
-        }
-        mOrientation.select(0);
-        mOrientation.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mOrientation.addSelectionListener(new SelectionAdapter() {
-           @Override
-            public void widgetSelected(SelectionEvent e) {
-               onOrientationChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Density");
-        mDensityIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mDensity = new Combo(mDensityIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
-        Density[] dValues = Density.values();
-        mDensity.add("(Default)");
-        for (Density value : dValues) {
-            mDensity.add(value.getDisplayValue());
-        }
-        mDensity.select(0);
-        mDensity.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mDensity.addSelectionListener(new SelectionAdapter() {
-           @Override
-            public void widgetSelected(SelectionEvent e) {
-               onDensityChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Touch");
-        mTouchIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mTouch = new Combo(mTouchIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
-        TouchScreenType[] tstValues = TouchScreenType.values();
-        mTouch.add("(Default)");
-        for (TouchScreenType value : tstValues) {
-            mTouch.add(value.getDisplayValue());
-        }
-        mTouch.select(0);
-        mTouch.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mTouch.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onTouchChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Keybrd");
-        mKeyboardIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mKeyboard = new Combo(mKeyboardIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
-        KeyboardState[] ksValues = KeyboardState.values();
-        mKeyboard.add("(Default)");
-        for (KeyboardState value : ksValues) {
-            mKeyboard.add(value.getDisplayValue());
-        }
-        mKeyboard.select(0);
-        mKeyboard.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mKeyboard.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onKeyboardChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Input");
-        mTextInputIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mTextInput = new Combo(mTextInputIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
-        TextInputMethod[] timValues = TextInputMethod.values();
-        mTextInput.add("(Default)");
-        for (TextInputMethod value : timValues) {
-            mTextInput.add(value.getDisplayValue());
-        }
-        mTextInput.select(0);
-        mTextInput.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mTextInput.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onTextInputChange();
-            }
-        });
-
-        new Label(topParent, SWT.NONE).setText("Nav");
-        mNavigationIcon = createControlComposite(topParent, true /* grab_horizontal */);
-        mNavigation = new Combo(mNavigationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
-        NavigationMethod[] nValues = NavigationMethod.values();
-        mNavigation.add("(Default)");
-        for (NavigationMethod value : nValues) {
-            mNavigation.add(value.getDisplayValue());
-        }
-        mNavigation.select(0);
-        mNavigation.setLayoutData(new GridData(
-                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
-        mNavigation.addSelectionListener(new SelectionAdapter() {
-            @Override
-             public void widgetSelected(SelectionEvent e) {
-                onNavigationChange();
-            }
-        });
-
-        Composite labelParent = new Composite(topParent, SWT.NONE);
-        labelParent.setLayout(gl = new GridLayout(8, false));
-        gl.marginWidth = gl.marginHeight = 0;
-        labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
-        gd.horizontalSpan = cols;
-
-        new Label(labelParent, SWT.NONE).setText("Editing config:");
-        mCurrentLayoutLabel = new Label(labelParent, SWT.NONE);
-        mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
-        gd.widthHint = 50;
-
-        new Label(labelParent, SWT.NONE).setText("Size");
-        mSizeIcon = createControlComposite(labelParent, false);
-        Composite sizeParent = new Composite(mSizeIcon.getParent(), SWT.NONE);
-        sizeParent.setLayout(gl = new GridLayout(3, false));
-        gl.marginWidth = gl.marginHeight = 0;
-        gl.horizontalSpacing = 0;
-
-        mSize1 = new Text(sizeParent, SWT.BORDER);
-        mSize1.setLayoutData(gd = new GridData());
-        gd.widthHint = 30;
-        new Label(sizeParent, SWT.NONE).setText("x");
-        mSize2 = new Text(sizeParent, SWT.BORDER);
-        mSize2.setLayoutData(gd = new GridData());
-        gd.widthHint = 30;
-
-        DimensionVerifier verifier = new DimensionVerifier();
-        mSize1.addVerifyListener(verifier);
-        mSize2.addVerifyListener(verifier);
-
-        SelectionListener sl = new SelectionListener() {
-            public void widgetDefaultSelected(SelectionEvent e) {
-                onSizeChange();
-            }
-            public void widgetSelected(SelectionEvent e) {
-                onSizeChange();
-            }
-        };
-
-        mSize1.addSelectionListener(sl);
-        mSize2.addSelectionListener(sl);
-
-        ModifyListener sizeModifyListener = new ModifyListener() {
-            public void modifyText(ModifyEvent e) {
-                onSizeChange();
-            }
-        };
-
-        mSize1.addModifyListener(sizeModifyListener);
-        mSize2.addModifyListener(sizeModifyListener);
-
-        // first separator
-        Label separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL);
-        separator.setLayoutData(gd = new GridData(
-                GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
-        gd.heightHint = 0;
-
-        mThemeCombo = new Combo(labelParent, SWT.READ_ONLY | SWT.DROP_DOWN);
-        mThemeCombo.setEnabled(false);
-        updateUIFromResources();
-        mThemeCombo.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                onThemeChange();
-            }
-        });
-
-        // second separator
-        separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL);
-        separator.setLayoutData(gd = new GridData(
-                GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
-        gd.heightHint = 0;
-
-        mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT);
-        mCreateButton.setText("Create...");
-        mCreateButton.setEnabled(false);
-        mCreateButton.addSelectionListener(new SelectionAdapter() {
-            @Override
-            public void widgetSelected(SelectionEvent e) {
-                LayoutCreatorDialog dialog = new LayoutCreatorDialog(mCreateButton.getShell(),
-                        mEditedFile.getName(),
-                        Sdk.getCurrent().getTarget(mEditedFile.getProject()), mCurrentConfig);
-                if (dialog.open() == Dialog.OK) {
-                    final FolderConfiguration config = new FolderConfiguration();
-                    dialog.getConfiguration(config);
-
-                    createAlternateLayout(config);
-                }
-            }
-        });
+        mConfigComposite = new ConfigurationComposite(this, parent, SWT.NONE);
 
         // create a new composite that will contain the standard editor controls.
         Composite editorParent = new Composite(parent, SWT.NONE);
@@ -689,7 +335,7 @@
             FileEditorInput fileInput = (FileEditorInput)input;
             mEditedFile = fileInput.getFile();
 
-            updateUIFromResources();
+            mConfigComposite.updateUIFromResources();
 
             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), this);
         } else {
@@ -873,49 +519,12 @@
         setConfiguration(configuration, true /*force*/);
 
         // enable the create button if the current and edited config are not equals
-        mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
+        mConfigComposite.setEnabledCreate(
+                mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
     }
 
     public Rectangle getBounds() {
-        ScreenOrientation orientation = null;
-        if (mOrientation.getSelectionIndex() == 0) {
-            orientation = ScreenOrientation.PORTRAIT;
-        } else {
-            orientation = ScreenOrientation.getByIndex(
-                    mOrientation.getSelectionIndex() - 1);
-        }
-
-        int s1, s2;
-
-        // get the size from the UI controls. If it fails, revert to default values.
-        try {
-            s1 = Integer.parseInt(mSize1.getText().trim());
-        } catch (NumberFormatException e) {
-            s1 = 480;
-        }
-
-        try {
-            s2 = Integer.parseInt(mSize2.getText().trim());
-        } catch (NumberFormatException e) {
-            s2 = 320;
-        }
-
-        // make sure s1 is bigger than s2
-        if (s1 < s2) {
-            int tmp = s1;
-            s1 = s2;
-            s2 = tmp;
-        }
-
-        switch (orientation) {
-            default:
-            case PORTRAIT:
-                return new Rectangle(0, 0, s2, s1);
-            case LANDSCAPE:
-                return new Rectangle(0, 0, s1, s2);
-            case SQUARE:
-                return new Rectangle(0, 0, s1, s1);
-        }
+        return mConfigComposite.getScreenBounds();
     }
 
     /**
@@ -960,7 +569,8 @@
                     projectRes.loadAll();
 
                     // get the project resource values based on the current config
-                    mConfiguredProjectRes = projectRes.getConfiguredResources(mCurrentConfig);
+                    mConfiguredProjectRes = projectRes.getConfiguredResources(
+                            mConfigComposite.getCurrentConfig());
                 }
 
                 configuredProjectResources = mConfiguredProjectRes;
@@ -975,17 +585,15 @@
 
             if (configuredProjectResources != null && frameworkResources != null) {
                 // get the selected theme
-                int themeIndex = mThemeCombo.getSelectionIndex();
-                if (themeIndex != -1) {
-                    String theme = mThemeCombo.getItem(themeIndex);
-
+                String theme = mConfigComposite.getTheme();
+                if (theme != null) {
                     // Render a single object as described by the ViewElementDescriptor.
                     WidgetPullParser parser = new WidgetPullParser(descriptor);
                     ILayoutResult result = computeLayout(bridge, parser,
                             null /* projectKey */,
                             300 /* width */, 300 /* height */, 160 /*density*/,
                             160.f /*xdpi*/, 160.f /*ydpi*/, theme,
-                            themeIndex >= mPlatformThemeCount /*isProjectTheme*/,
+                            mConfigComposite.isProjectTheme(),
                             configuredProjectResources, frameworkResources, projectCallback,
                             null /* logger */);
 
@@ -1080,231 +688,13 @@
      * @param force Whether the UI should be changed to exactly match the received configuration.
      */
     void setConfiguration(FolderConfiguration config, boolean force) {
-        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets.
-
         mEditedConfig = config;
         mConfiguredFrameworkRes = mConfiguredProjectRes = null;
 
-        mCountryIcon.setImage(mMatchImage);
-        CountryCodeQualifier countryQualifier = config.getCountryCodeQualifier();
-        if (countryQualifier != null) {
-            mCountry.setText(String.format("%1$d", countryQualifier.getCode()));
-            mCurrentConfig.setCountryCodeQualifier(countryQualifier);
-        } else if (force) {
-            mCountry.setText(""); //$NON-NLS-1$
-            mCurrentConfig.setCountryCodeQualifier(null);
-        } else if (mCountry.getText().length() > 0) {
-            mCountryIcon.setImage(mWarningImage);
-        }
+        mConfigComposite.setConfiguration(config, force);
 
-        mNetworkIcon.setImage(mMatchImage);
-        NetworkCodeQualifier networkQualifier = config.getNetworkCodeQualifier();
-        if (networkQualifier != null) {
-            mNetwork.setText(String.format("%1$d", networkQualifier.getCode()));
-            mCurrentConfig.setNetworkCodeQualifier(networkQualifier);
-        } else if (force) {
-            mNetwork.setText(""); //$NON-NLS-1$
-            mCurrentConfig.setNetworkCodeQualifier(null);
-        } else if (mNetwork.getText().length() > 0) {
-            mNetworkIcon.setImage(mWarningImage);
-        }
-
-        mLanguageIcon.setImage(mMatchImage);
-        LanguageQualifier languageQualifier = config.getLanguageQualifier();
-        if (languageQualifier != null) {
-            mLanguage.setText(languageQualifier.getValue());
-            mCurrentConfig.setLanguageQualifier(languageQualifier);
-        } else if (force) {
-            mLanguage.setText(""); //$NON-NLS-1$
-            mCurrentConfig.setLanguageQualifier(null);
-        } else if (mLanguage.getText().length() > 0) {
-            mLanguageIcon.setImage(mWarningImage);
-        }
-
-        mRegionIcon.setImage(mMatchImage);
-        RegionQualifier regionQualifier = config.getRegionQualifier();
-        if (regionQualifier != null) {
-            mRegion.setText(regionQualifier.getValue());
-            mCurrentConfig.setRegionQualifier(regionQualifier);
-        } else if (force) {
-            mRegion.setText(""); //$NON-NLS-1$
-            mCurrentConfig.setRegionQualifier(null);
-        } else if (mRegion.getText().length() > 0) {
-            mRegionIcon.setImage(mWarningImage);
-        }
-
-        mOrientationIcon.setImage(mMatchImage);
-        ScreenOrientationQualifier orientationQualifier = config.getScreenOrientationQualifier();
-        if (orientationQualifier != null) {
-            mOrientation.select(
-                    ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1);
-            mCurrentConfig.setScreenOrientationQualifier(orientationQualifier);
-        } else if (force) {
-            mOrientation.select(0);
-            mCurrentConfig.setScreenOrientationQualifier(null);
-        } else if (mOrientation.getSelectionIndex() != 0) {
-            mOrientationIcon.setImage(mWarningImage);
-        }
-
-        mDensityIcon.setImage(mMatchImage);
-        PixelDensityQualifier densityQualifier = config.getPixelDensityQualifier();
-        if (densityQualifier != null) {
-            mDensity.select(
-                    Density.getIndex(densityQualifier.getValue()) + 1);
-            mCurrentConfig.setPixelDensityQualifier(densityQualifier);
-        } else if (force) {
-            mOrientation.select(0);
-            mCurrentConfig.setPixelDensityQualifier(null);
-        } else if (mDensity.getSelectionIndex() != 0) {
-            mDensityIcon.setImage(mWarningImage);
-        }
-
-        mTouchIcon.setImage(mMatchImage);
-        TouchScreenQualifier touchQualifier = config.getTouchTypeQualifier();
-        if (touchQualifier != null) {
-            mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1);
-            mCurrentConfig.setTouchTypeQualifier(touchQualifier);
-        } else if (force) {
-            mTouch.select(0);
-            mCurrentConfig.setTouchTypeQualifier(null);
-        } else if (mTouch.getSelectionIndex() != 0) {
-            mTouchIcon.setImage(mWarningImage);
-        }
-
-        mKeyboardIcon.setImage(mMatchImage);
-        KeyboardStateQualifier keyboardQualifier = config.getKeyboardStateQualifier();
-        if (keyboardQualifier != null) {
-            mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1);
-            mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier);
-        } else if (force) {
-            mKeyboard.select(0);
-            mCurrentConfig.setKeyboardStateQualifier(null);
-        } else if (mKeyboard.getSelectionIndex() != 0) {
-            mKeyboardIcon.setImage(mWarningImage);
-        }
-
-        mTextInputIcon.setImage(mMatchImage);
-        TextInputMethodQualifier inputQualifier = config.getTextInputMethodQualifier();
-        if (inputQualifier != null) {
-            mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1);
-            mCurrentConfig.setTextInputMethodQualifier(inputQualifier);
-        } else if (force) {
-            mTextInput.select(0);
-            mCurrentConfig.setTextInputMethodQualifier(null);
-        } else if (mTextInput.getSelectionIndex() != 0) {
-            mTextInputIcon.setImage(mWarningImage);
-        }
-
-        mNavigationIcon.setImage(mMatchImage);
-        NavigationMethodQualifier navigationQualifiter = config.getNavigationMethodQualifier();
-        if (navigationQualifiter != null) {
-            mNavigation.select(
-                    NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1);
-            mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter);
-        } else if (force) {
-            mNavigation.select(0);
-            mCurrentConfig.setNavigationMethodQualifier(null);
-        } else if (mNavigation.getSelectionIndex() != 0) {
-            mNavigationIcon.setImage(mWarningImage);
-        }
-
-        mSizeIcon.setImage(mMatchImage);
-        ScreenDimensionQualifier sizeQualifier = config.getScreenDimensionQualifier();
-        if (sizeQualifier != null) {
-            mSize1.setText(String.format("%1$d", sizeQualifier.getValue1()));
-            mSize2.setText(String.format("%1$d", sizeQualifier.getValue2()));
-            mCurrentConfig.setScreenDimensionQualifier(sizeQualifier);
-        } else if (force) {
-            mSize1.setText(""); //$NON-NLS-1$
-            mSize2.setText(""); //$NON-NLS-1$
-            mCurrentConfig.setScreenDimensionQualifier(null);
-        } else if (mSize1.getText().length() > 0 && mSize2.getText().length() > 0) {
-            mSizeIcon.setImage(mWarningImage);
-        }
-
-        // update the string showing the folder name
-        String current = config.toDisplayString();
-        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
-
-        mDisableUpdates = false;
     }
 
-    /**
-     * Displays an error icon in front of all the non-null qualifiers.
-     */
-    void displayConfigError() {
-        mCountryIcon.setImage(mMatchImage);
-        CountryCodeQualifier countryQualifier = mCurrentConfig.getCountryCodeQualifier();
-        if (countryQualifier != null) {
-            mCountryIcon.setImage(mErrorImage);
-        }
-
-        mNetworkIcon.setImage(mMatchImage);
-        NetworkCodeQualifier networkQualifier = mCurrentConfig.getNetworkCodeQualifier();
-        if (networkQualifier != null) {
-            mNetworkIcon.setImage(mErrorImage);
-        }
-
-        mLanguageIcon.setImage(mMatchImage);
-        LanguageQualifier languageQualifier = mCurrentConfig.getLanguageQualifier();
-        if (languageQualifier != null) {
-            mLanguageIcon.setImage(mErrorImage);
-        }
-
-        mRegionIcon.setImage(mMatchImage);
-        RegionQualifier regionQualifier = mCurrentConfig.getRegionQualifier();
-        if (regionQualifier != null) {
-            mRegionIcon.setImage(mErrorImage);
-        }
-
-        mOrientationIcon.setImage(mMatchImage);
-        ScreenOrientationQualifier orientationQualifier =
-            mCurrentConfig.getScreenOrientationQualifier();
-        if (orientationQualifier != null) {
-            mOrientationIcon.setImage(mErrorImage);
-        }
-
-        mDensityIcon.setImage(mMatchImage);
-        PixelDensityQualifier densityQualifier = mCurrentConfig.getPixelDensityQualifier();
-        if (densityQualifier != null) {
-            mDensityIcon.setImage(mErrorImage);
-        }
-
-        mTouchIcon.setImage(mMatchImage);
-        TouchScreenQualifier touchQualifier = mCurrentConfig.getTouchTypeQualifier();
-        if (touchQualifier != null) {
-            mTouchIcon.setImage(mErrorImage);
-        }
-
-        mKeyboardIcon.setImage(mMatchImage);
-        KeyboardStateQualifier keyboardQualifier = mCurrentConfig.getKeyboardStateQualifier();
-        if (keyboardQualifier != null) {
-            mKeyboardIcon.setImage(mErrorImage);
-        }
-
-        mTextInputIcon.setImage(mMatchImage);
-        TextInputMethodQualifier inputQualifier = mCurrentConfig.getTextInputMethodQualifier();
-        if (inputQualifier != null) {
-            mTextInputIcon.setImage(mErrorImage);
-        }
-
-        mNavigationIcon.setImage(mMatchImage);
-        NavigationMethodQualifier navigationQualifiter =
-            mCurrentConfig.getNavigationMethodQualifier();
-        if (navigationQualifiter != null) {
-            mNavigationIcon.setImage(mErrorImage);
-        }
-
-        mSizeIcon.setImage(mMatchImage);
-        ScreenDimensionQualifier sizeQualifier = mCurrentConfig.getScreenDimensionQualifier();
-        if (sizeQualifier != null) {
-            mSizeIcon.setImage(mErrorImage);
-        }
-
-        // update the string showing the folder name
-        String current = mCurrentConfig.toDisplayString();
-        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
-    }
 
     @Override
     UiDocumentNode getModel() {
@@ -1316,275 +706,13 @@
         PaletteFactory.createPaletteRoot(mPaletteRoot, mLayoutEditor.getTargetData());
     }
 
-    private void onCountryCodeChange() {
-        // because mCountry triggers onCountryCodeChange at each modification, calling setText()
-        // will trigger notifications, and we don't want that.
-        if (mDisableUpdates == true) {
-            return;
-        }
-
-        // update the current config
-        String value = mCountry.getText();
-
-        // empty string, means no qualifier.
-        if (value.length() == 0) {
-            mCurrentConfig.setCountryCodeQualifier(null);
-        } else {
-            try {
-                CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier(
-                        CountryCodeQualifier.getFolderSegment(Integer.parseInt(value)));
-                if (qualifier != null) {
-                    mCurrentConfig.setCountryCodeQualifier(qualifier);
-                } else {
-                    // Failure! Looks like the value is wrong (for instance a one letter string).
-                    // We do nothing in this case.
-                    mCountryIcon.setImage(mErrorImage);
-                    return;
-                }
-            } catch (NumberFormatException e) {
-                // Looks like the code is not a number. This should not happen since the text
-                // field has a VerifyListener that prevents it.
-                mCurrentConfig.setCountryCodeQualifier(null);
-                mCountryIcon.setImage(mErrorImage);
-            }
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onNetworkCodeChange() {
-        // because mNetwork triggers onNetworkCodeChange at each modification, calling setText()
-        // will trigger notifications, and we don't want that.
-        if (mDisableUpdates == true) {
-            return;
-        }
-
-        // update the current config
-        String value = mNetwork.getText();
-
-        // empty string, means no qualifier.
-        if (value.length() == 0) {
-            mCurrentConfig.setNetworkCodeQualifier(null);
-        } else {
-            try {
-                NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier(
-                        NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value)));
-                if (qualifier != null) {
-                    mCurrentConfig.setNetworkCodeQualifier(qualifier);
-                } else {
-                    // Failure! Looks like the value is wrong (for instance a one letter string).
-                    // We do nothing in this case.
-                    mNetworkIcon.setImage(mErrorImage);
-                    return;
-                }
-            } catch (NumberFormatException e) {
-                // Looks like the code is not a number. This should not happen since the text
-                // field has a VerifyListener that prevents it.
-                mCurrentConfig.setNetworkCodeQualifier(null);
-                mNetworkIcon.setImage(mErrorImage);
-            }
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    /**
-     * Call back for language combo selection
-     */
-    private void onLanguageChange() {
-        // because mLanguage triggers onLanguageChange at each modification, the filling
-        // of the combo with data will trigger notifications, and we don't want that.
-        if (mDisableUpdates == true) {
-            return;
-        }
-
-        // update the current config
-        String value = mLanguage.getText();
-
-        updateRegionUi(null /* projectResources */, null /* frameworkResources */);
-
-        // empty string, means no qualifier.
-        if (value.length() == 0) {
-            mCurrentConfig.setLanguageQualifier(null);
-        } else {
-            LanguageQualifier qualifier = null;
-            String segment = LanguageQualifier.getFolderSegment(value);
-            if (segment != null) {
-                qualifier = LanguageQualifier.getQualifier(segment);
-            }
-
-            if (qualifier != null) {
-                mCurrentConfig.setLanguageQualifier(qualifier);
-            } else {
-                // Failure! Looks like the value is wrong (for instance a one letter string).
-                mCurrentConfig.setLanguageQualifier(null);
-                mLanguageIcon.setImage(mErrorImage);
-            }
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onRegionChange() {
-        // because mRegion triggers onRegionChange at each modification, the filling
-        // of the combo with data will trigger notifications, and we don't want that.
-        if (mDisableUpdates == true) {
-            return;
-        }
-
-        // update the current config
-        String value = mRegion.getText();
-
-        // empty string, means no qualifier.
-        if (value.length() == 0) {
-            mCurrentConfig.setRegionQualifier(null);
-        } else {
-            RegionQualifier qualifier = null;
-            String segment = RegionQualifier.getFolderSegment(value);
-            if (segment != null) {
-                qualifier = RegionQualifier.getQualifier(segment);
-            }
-
-            if (qualifier != null) {
-                mCurrentConfig.setRegionQualifier(qualifier);
-            } else {
-                // Failure! Looks like the value is wrong (for instance a one letter string).
-                mCurrentConfig.setRegionQualifier(null);
-                mRegionIcon.setImage(mErrorImage);
-            }
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onOrientationChange() {
-        // update the current config
-        int index = mOrientation.getSelectionIndex();
-        if (index != 0) {
-            mCurrentConfig.setScreenOrientationQualifier(new ScreenOrientationQualifier(
-                ScreenOrientation.getByIndex(index-1)));
-        } else {
-            mCurrentConfig.setScreenOrientationQualifier(null);
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onDensityChange() {
-        int index = mDensity.getSelectionIndex();
-        if (index != 0) {
-            mCurrentConfig.setPixelDensityQualifier((new PixelDensityQualifier(
-                Density.getByIndex(index-1))));
-        } else {
-            mCurrentConfig.setPixelDensityQualifier(null);
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onTouchChange() {
-        // update the current config
-        int index = mTouch.getSelectionIndex();
-        if (index != 0) {
-            mCurrentConfig.setTouchTypeQualifier(new TouchScreenQualifier(
-                TouchScreenType.getByIndex(index-1)));
-        } else {
-            mCurrentConfig.setTouchTypeQualifier(null);
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onKeyboardChange() {
-        // update the current config
-        int index = mKeyboard.getSelectionIndex();
-        if (index != 0) {
-            mCurrentConfig.setKeyboardStateQualifier(new KeyboardStateQualifier(
-                KeyboardState.getByIndex(index-1)));
-        } else {
-            mCurrentConfig.setKeyboardStateQualifier(null);
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onTextInputChange() {
-        // update the current config
-        int index = mTextInput.getSelectionIndex();
-        if (index != 0) {
-            mCurrentConfig.setTextInputMethodQualifier(new TextInputMethodQualifier(
-                TextInputMethod.getByIndex(index-1)));
-        } else {
-            mCurrentConfig.setTextInputMethodQualifier(null);
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onNavigationChange() {
-        // update the current config
-        int index = mNavigation.getSelectionIndex();
-        if (index != 0) {
-            mCurrentConfig.setNavigationMethodQualifier(new NavigationMethodQualifier(
-                NavigationMethod.getByIndex(index-1)));
-        } else {
-            mCurrentConfig.setNavigationMethodQualifier(null);
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
-
-    private void onSizeChange() {
-        // because mSize1 and mSize2 trigger onSizeChange at each modification, calling setText()
-        // will trigger notifications, and we don't want that.
-        if (mDisableUpdates == true) {
-            return;
-        }
-
-        // update the current config
-        String size1 = mSize1.getText();
-        String size2 = mSize2.getText();
-
-        // if only one of the strings is empty, do nothing
-        if ((size1.length() == 0) ^ (size2.length() == 0)) {
-            mSizeIcon.setImage(mErrorImage);
-            return;
-        } else if (size1.length() == 0 && size2.length() == 0) {
-            // both sizes are empty: remove the qualifier.
-            mCurrentConfig.setScreenDimensionQualifier(null);
-        } else {
-            ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1,
-                    size2);
-
-            if (qualifier != null) {
-                mCurrentConfig.setScreenDimensionQualifier(qualifier);
-            } else {
-                // Failure! Looks like the value is wrong.
-                // we do nothing in this case.
-                return;
-            }
-        }
-
-        // look for a file to open/create
-        onConfigurationChange();
-    }
 
 
     /**
      * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it.
      * <p/>If there is no match, notify the user.
      */
-    private void onConfigurationChange() {
+    public void onConfigurationChange() {
         mConfiguredFrameworkRes = mConfiguredProjectRes = null;
 
         if (mEditedFile == null || mEditedConfig == null) {
@@ -1600,7 +728,7 @@
         if (resources != null) {
             match = resources.getMatchingFile(mEditedFile.getName(),
                                               ResourceFolderType.LAYOUT,
-                                              mCurrentConfig);
+                                              mConfigComposite.getCurrentConfig());
         }
 
         if (match != null) {
@@ -1623,65 +751,46 @@
             setConfiguration(mEditedConfig, false /*force*/);
 
             // enable the create button if the current and edited config are not equals
-            mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
+            mConfigComposite.setEnabledCreate(
+                    mEditedConfig.equals(mConfigComposite.getCurrentConfig()) == false);
 
             // Even though the layout doesn't change, the config changed, and referenced
             // resources need to be updated.
             recomputeLayout();
         } else {
             // update the configuration icons with the new edited config.
-            displayConfigError();
+            mConfigComposite.displayConfigError();
 
             // enable the Create button
-            mCreateButton.setEnabled(true);
+            mConfigComposite.setEnabledCreate(true);
 
             // display the error.
+            FolderConfiguration currentConfig = mConfigComposite.getCurrentConfig();
             String message = String.format(
                     "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.",
-                    mCurrentConfig.toDisplayString(),
-                    mCurrentConfig.getFolderName(ResourceFolderType.LAYOUT,
+                    currentConfig.toDisplayString(),
+                    currentConfig.getFolderName(ResourceFolderType.LAYOUT,
                             Sdk.getCurrent().getTarget(mEditedFile.getProject())),
                     mEditedFile.getName());
             showErrorInEditor(message);
         }
     }
 
-    private void onThemeChange() {
-        int themeIndex = mThemeCombo.getSelectionIndex();
-        if (themeIndex != -1) {
-            String theme = mThemeCombo.getItem(themeIndex);
-
-            if (theme.equals(THEME_SEPARATOR)) {
-                mThemeCombo.select(0);
-            }
-
-            recomputeLayout();
-        }
+    public void onThemeChange() {
+        recomputeLayout();
     }
 
-    /**
-     * Creates a composite with no margin/spacing, and puts a {@link Label} in it with the matching
-     * icon.
-     * @param parent the parent to receive the composite
-     * @return the created {@link Label} object.
-     */
-    private Label createControlComposite(Composite parent, boolean grab) {
-        GridLayout gl;
+    public void onCreate() {
+        LayoutCreatorDialog dialog = new LayoutCreatorDialog(mParent.getShell(),
+                mEditedFile.getName(),
+                Sdk.getCurrent().getTarget(mEditedFile.getProject()),
+                mConfigComposite.getCurrentConfig());
+        if (dialog.open() == Dialog.OK) {
+            final FolderConfiguration config = new FolderConfiguration();
+            dialog.getConfiguration(config);
 
-        Composite composite = new Composite(parent, SWT.NONE);
-        composite.setLayout(gl = new GridLayout(2, false));
-        gl.marginHeight = gl.marginWidth = 0;
-        gl.horizontalSpacing = 0;
-        if (grab) {
-            composite.setLayoutData(
-                    new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+            createAlternateLayout(config);
         }
-
-        // create the label
-        Label icon = new Label(composite, SWT.NONE);
-        icon.setImage(mMatchImage);
-
-        return icon;
     }
 
     /**
@@ -1760,19 +869,14 @@
                     }
 
                     // get the resources of the file's project.
-                    if (mConfiguredProjectRes == null) {
-                        // make sure they are loaded
-                        projectRes.loadAll();
-
-                        // get the project resource values based on the current config
-                        mConfiguredProjectRes = projectRes.getConfiguredResources(mCurrentConfig);
-                    }
+                    Map<String, Map<String, IResourceValue>> configuredProjectRes =
+                        getConfiguredProjectResources();
 
                     // get the framework resources
                     Map<String, Map<String, IResourceValue>> frameworkResources =
                         getConfiguredFrameworkResources();
 
-                    if (mConfiguredProjectRes != null && frameworkResources != null) {
+                    if (configuredProjectRes != null && frameworkResources != null) {
                         if (mProjectCallback == null) {
                             mProjectCallback = new ProjectCallback(
                                     bridge.classLoader, projectRes, iProject);
@@ -1801,19 +905,19 @@
                         }
 
                         // get the selected theme
-                        int themeIndex = mThemeCombo.getSelectionIndex();
-                        if (themeIndex != -1) {
-                            String theme = mThemeCombo.getItem(themeIndex);
+                        String theme = mConfigComposite.getTheme();
+                        if (theme != null) {
 
                             // Compute the layout
                             UiElementPullParser parser = new UiElementPullParser(getModel());
                             Rectangle rect = getBounds();
-                            boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
+                            boolean isProjectTheme = mConfigComposite.isProjectTheme();
 
                             // FIXME pass the density/dpi from somewhere (resource config or skin).
                             // For now, get it from the config
                             int density = Density.MEDIUM.getDpiValue();
-                            PixelDensityQualifier qual = mCurrentConfig.getPixelDensityQualifier();
+                            PixelDensityQualifier qual =
+                                mConfigComposite.getCurrentConfig().getPixelDensityQualifier();
                             if (qual != null) {
                                 int d = qual.getValue().getDpiValue();
                                 if (d > 0) {
@@ -1825,7 +929,7 @@
                                     iProject /* projectKey */,
                                     rect.width, rect.height, density, density, density,
                                     theme, isProjectTheme,
-                                    mConfiguredProjectRes, frameworkResources, mProjectCallback,
+                                    configuredProjectRes, frameworkResources, mProjectCallback,
                                     mLogger);
 
                             // update the UiElementNode with the layout info.
@@ -1985,244 +1089,7 @@
         // nothing to be done here for now.
     }
 
-    /**
-     * Updates the UI from values in the resources, such as languages, regions, themes, etc...
-     * This must be called from the UI thread.
-     */
-    private void updateUIFromResources() {
-
-        ResourceManager manager = ResourceManager.getInstance();
-
-        ProjectResources frameworkProject = getFrameworkResources();
-
-        mDisableUpdates = true;
-
-        // Reset stuff
-        int selection = mThemeCombo.getSelectionIndex();
-        mThemeCombo.removeAll();
-        mPlatformThemeCount = 0;
-        mLanguage.removeAll();
-
-        Set<String> languages = new HashSet<String>();
-        ArrayList<String> themes = new ArrayList<String>();
-
-        // get the themes, and languages from the Framework.
-        if (frameworkProject != null) {
-            // get the configured resources for the framework
-            Map<String, Map<String, IResourceValue>> frameworResources =
-                getConfiguredFrameworkResources();
-
-            if (frameworResources != null) {
-                // get the styles.
-                Map<String, IResourceValue> styles = frameworResources.get(
-                        ResourceType.STYLE.getName());
-
-
-                // collect the themes out of all the styles.
-                for (IResourceValue value : styles.values()) {
-                    String name = value.getName();
-                    if (name.startsWith("Theme.") || name.equals("Theme")) {
-                        themes.add(value.getName());
-                        mPlatformThemeCount++;
-                    }
-                }
-
-                // sort them and add them to the combo
-                Collections.sort(themes);
-
-                for (String theme : themes) {
-                    mThemeCombo.add(theme);
-                }
-
-                mPlatformThemeCount = themes.size();
-                themes.clear();
-            }
-            // now get the languages from the framework.
-            Set<String> frameworkLanguages = frameworkProject.getLanguages();
-            if (frameworkLanguages != null) {
-                languages.addAll(frameworkLanguages);
-            }
-        }
-
-        // now get the themes and languages from the project.
-        ProjectResources project = null;
-        if (mEditedFile != null) {
-            project = manager.getProjectResources(mEditedFile.getProject());
-
-            // in cases where the opened file is not linked to a project, this could be null.
-            if (project != null) {
-                // get the configured resources for the project
-                if (mConfiguredProjectRes == null) {
-                    // make sure they are loaded
-                    project.loadAll();
-
-                    // get the project resource values based on the current config
-                    mConfiguredProjectRes = project.getConfiguredResources(mCurrentConfig);
-                }
-
-                if (mConfiguredProjectRes != null) {
-                    // get the styles.
-                    Map<String, IResourceValue> styleMap = mConfiguredProjectRes.get(
-                            ResourceType.STYLE.getName());
-
-                    if (styleMap != null) {
-                        // collect the themes out of all the styles, ie styles that extend,
-                        // directly or indirectly a platform theme.
-                        for (IResourceValue value : styleMap.values()) {
-                            if (isTheme(value, styleMap)) {
-                                themes.add(value.getName());
-                            }
-                        }
-
-                        // sort them and add them the to the combo.
-                        if (mPlatformThemeCount > 0 && themes.size() > 0) {
-                            mThemeCombo.add(THEME_SEPARATOR);
-                        }
-
-                        Collections.sort(themes);
-
-                        for (String theme : themes) {
-                            mThemeCombo.add(theme);
-                        }
-                    }
-                }
-
-                // now get the languages from the project.
-                Set<String> projectLanguages = project.getLanguages();
-                if (projectLanguages != null) {
-                    languages.addAll(projectLanguages);
-                }
-            }
-        }
-
-        // add the languages to the Combo
-        for (String language : languages) {
-            mLanguage.add(language);
-        }
-
-        mDisableUpdates = false;
-
-        // and update the Region UI based on the current language
-        updateRegionUi(project, frameworkProject);
-
-        // handle default selection of themes
-        if (mThemeCombo.getItemCount() > 0) {
-            mThemeCombo.setEnabled(true);
-            if (selection == -1) {
-                selection = 0;
-            }
-
-            if (mThemeCombo.getItemCount() <= selection) {
-                mThemeCombo.select(0);
-            } else {
-                mThemeCombo.select(selection);
-            }
-        } else {
-            mThemeCombo.setEnabled(false);
-        }
-    }
-
-    /**
-     * Returns whether the given <var>style</var> is a theme.
-     * This is done by making sure the parent is a theme.
-     * @param value the style to check
-     * @param styleMap the map of styles for the current project. Key is the style name.
-     * @return True if the given <var>style</var> is a theme.
-     */
-    private boolean isTheme(IResourceValue value, Map<String, IResourceValue> styleMap) {
-        if (value instanceof IStyleResourceValue) {
-            IStyleResourceValue style = (IStyleResourceValue)value;
-
-            boolean frameworkStyle = false;
-            String parentStyle = style.getParentStyle();
-            if (parentStyle == null) {
-                // if there is no specified parent style we look an implied one.
-                // For instance 'Theme.light' is implied child style of 'Theme',
-                // and 'Theme.light.fullscreen' is implied child style of 'Theme.light'
-                String name = style.getName();
-                int index = name.lastIndexOf('.');
-                if (index != -1) {
-                    parentStyle = name.substring(0, index);
-                }
-            } else {
-                // remove the useless @ if it's there
-                if (parentStyle.startsWith("@")) {
-                    parentStyle = parentStyle.substring(1);
-                }
-
-                // check for framework identifier.
-                if (parentStyle.startsWith("android:")) {
-                    frameworkStyle = true;
-                    parentStyle = parentStyle.substring("android:".length());
-                }
-
-                // at this point we could have the format style/<name>. we want only the name
-                if (parentStyle.startsWith("style/")) {
-                    parentStyle = parentStyle.substring("style/".length());
-                }
-            }
-
-            if (frameworkStyle) {
-                // if the parent is a framework style, it has to be 'Theme' or 'Theme.*'
-                return parentStyle.equals("Theme") || parentStyle.startsWith("Theme.");
-            } else {
-                // if it's a project style, we check this is a theme.
-                value = styleMap.get(parentStyle);
-                if (value != null) {
-                    return isTheme(value, styleMap);
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Update the Region UI widget based on the current language selection
-     * @param projectResources the project resources or {@code null}.
-     * @param frameworkResources the framework resource or {@code null}
-     */
-    private void updateRegionUi(ProjectResources projectResources,
-            ProjectResources frameworkResources) {
-        if (projectResources == null && mEditedFile != null) {
-            projectResources = ResourceManager.getInstance().getProjectResources(
-                    mEditedFile.getProject());
-        }
-
-        if (frameworkResources == null) {
-            frameworkResources = getFrameworkResources();
-        }
-
-        String currentLanguage = mLanguage.getText();
-
-        Set<String> set = null;
-
-        if (projectResources != null) {
-            set = projectResources.getRegions(currentLanguage);
-        }
-
-        if (frameworkResources != null) {
-            if (set != null) {
-                Set<String> set2 = frameworkResources.getRegions(currentLanguage);
-                set.addAll(set2);
-            } else {
-                set = frameworkResources.getRegions(currentLanguage);
-            }
-        }
-
-        if (set != null) {
-            mDisableUpdates = true;
-
-            mRegion.removeAll();
-            for (String region : set) {
-                mRegion.add(region);
-            }
-
-            mDisableUpdates = false;
-        }
-    }
-
-    private Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {
+    public Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources() {
         if (mConfiguredFrameworkRes == null) {
             ProjectResources frameworkRes = getFrameworkResources();
 
@@ -2231,14 +1098,62 @@
             }
 
             // get the framework resource values based on the current config
-            mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(mCurrentConfig);
+            mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
+                    mConfigComposite.getCurrentConfig());
         }
 
         return mConfiguredFrameworkRes;
     }
 
+    public Map<String, Map<String, IResourceValue>> getConfiguredProjectResources() {
+        if (mConfiguredProjectRes == null) {
+            ProjectResources project = getProjectResources();
+
+            // make sure they are loaded
+            project.loadAll();
+
+            // get the project resource values based on the current config
+            mConfiguredProjectRes = project.getConfiguredResources(
+                    mConfigComposite.getCurrentConfig());
+        }
+
+        return mConfiguredProjectRes;
+    }
+
     /**
-     * Creates a new layout file from the specificed {@link FolderConfiguration}.
+     * Returns a {@link ProjectResources} for the framework resources.
+     * @return the framework resources or null if not found.
+     */
+    public ProjectResources getFrameworkResources() {
+        if (mEditedFile != null) {
+            Sdk currentSdk = Sdk.getCurrent();
+            if (currentSdk != null) {
+                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
+
+                if (target != null) {
+                    AndroidTargetData data = currentSdk.getTargetData(target);
+
+                    if (data != null) {
+                        return data.getFrameworkResources();
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public ProjectResources getProjectResources() {
+        if (mEditedFile != null) {
+            ResourceManager manager = ResourceManager.getInstance();
+            return manager.getProjectResources(mEditedFile.getProject());
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a new layout file from the specified {@link FolderConfiguration}.
      */
     private void createAlternateLayout(final FolderConfiguration config) {
         new Job("Create Alternate Resource") {
@@ -2295,7 +1210,7 @@
                     // to trigger the edit of the new file.
                     res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() {
                         public void done() {
-                            mCurrentConfig.set(config);
+                            mConfigComposite.setConfig(config);
                             mParent.getDisplay().asyncExec(new Runnable() {
                                 public void run() {
                                     onConfigurationChange();
@@ -2358,29 +1273,6 @@
     }
 
     /**
-     * Returns a {@link ProjectResources} for the framework resources.
-     * @return the framework resources or null if not found.
-     */
-    private ProjectResources getFrameworkResources() {
-        if (mEditedFile != null) {
-            Sdk currentSdk = Sdk.getCurrent();
-            if (currentSdk != null) {
-                IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
-
-                if (target != null) {
-                    AndroidTargetData data = currentSdk.getTargetData(target);
-
-                    if (data != null) {
-                        return data.getFrameworkResources();
-                    }
-                }
-            }
-        }
-
-        return null;
-    }
-
-    /**
      * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
      * the implementation API level.
      */
@@ -2423,4 +1315,5 @@
                     mLogger);
         }
     }
+
 }
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
new file mode 100644
index 0000000..52dfd5c
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
@@ -0,0 +1,1268 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.resources.ResourceType;
+import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
+import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier;
+import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier.KeyboardState;
+import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier.NavigationMethod;
+import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier.Density;
+import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier.ScreenOrientation;
+import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier.TextInputMethod;
+import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier.TouchScreenType;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.DimensionVerifier;
+import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.LanguageRegionVerifier;
+import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.MobileCodeVerifier;
+import com.android.layoutlib.api.IResourceValue;
+import com.android.layoutlib.api.IStyleResourceValue;
+
+import org.eclipse.draw2d.geometry.Rectangle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class ConfigurationComposite extends Composite {
+
+    private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
+
+    private Text mCountry;
+    private Text mNetwork;
+    private Combo mLanguage;
+    private Combo mRegion;
+    private Combo mOrientation;
+    private Combo mDensity;
+    private Combo mTouch;
+    private Combo mKeyboard;
+    private Combo mTextInput;
+    private Combo mNavigation;
+    private Text mSize1;
+    private Text mSize2;
+    private Combo mThemeCombo;
+    private Button mCreateButton;
+
+    private Label mCountryIcon;
+    private Label mNetworkIcon;
+    private Label mLanguageIcon;
+    private Label mRegionIcon;
+    private Label mOrientationIcon;
+    private Label mDensityIcon;
+    private Label mTouchIcon;
+    private Label mKeyboardIcon;
+    private Label mTextInputIcon;
+    private Label mNavigationIcon;
+    private Label mSizeIcon;
+
+    private Label mCurrentLayoutLabel;
+
+    private Image mWarningImage;
+    private Image mMatchImage;
+    private Image mErrorImage;
+
+    private int mPlatformThemeCount = 0;
+    private boolean mDisableUpdates = false;
+
+    /** The {@link FolderConfiguration} representing the state of the UI controls */
+    private final FolderConfiguration mCurrentConfig = new FolderConfiguration();
+    private final IConfigListener mListener;
+
+    public interface IConfigListener {
+        void onConfigurationChange();
+        void onThemeChange();
+        void onCreate();
+
+        ProjectResources getProjectResources();
+        ProjectResources getFrameworkResources();
+        Map<String, Map<String, IResourceValue>> getConfiguredProjectResources();
+        Map<String, Map<String, IResourceValue>> getConfiguredFrameworkResources();
+    }
+
+    public ConfigurationComposite(IConfigListener listener, Composite parent, int style) {
+        super(parent, style);
+        mListener = listener;
+
+        IconFactory factory = IconFactory.getInstance();
+        mWarningImage = factory.getIcon("warning"); //$NON-NLS-1$
+        mMatchImage = factory.getIcon("match"); //$NON-NLS-1$
+        mErrorImage = factory.getIcon("error"); //$NON-NLS-1$
+
+        GridLayout gl;
+        GridData gd;
+        int cols = 10;
+
+        setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+        setLayout(gl = new GridLayout(cols, false));
+
+        new Label(this, SWT.NONE).setText("MCC");
+        mCountryIcon = createControlComposite(this, true /* grab_horizontal */);
+        mCountry = new Text(mCountryIcon.getParent(), SWT.BORDER);
+        mCountry.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mCountry.addVerifyListener(new MobileCodeVerifier());
+        mCountry.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetDefaultSelected(SelectionEvent e) {
+                onCountryCodeChange();
+            }
+        });
+        mCountry.addModifyListener(new ModifyListener() {
+            public void modifyText(ModifyEvent e) {
+                onCountryCodeChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("MNC");
+        mNetworkIcon = createControlComposite(this, true /* grab_horizontal */);
+        mNetwork = new Text(mNetworkIcon.getParent(), SWT.BORDER);
+        mNetwork.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mNetwork.addVerifyListener(new MobileCodeVerifier());
+        mNetwork.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetDefaultSelected(SelectionEvent e) {
+                onNetworkCodeChange();
+            }
+        });
+        mNetwork.addModifyListener(new ModifyListener() {
+            public void modifyText(ModifyEvent e) {
+                onNetworkCodeChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Lang");
+        mLanguageIcon = createControlComposite(this, true /* grab_horizontal */);
+        mLanguage = new Combo(mLanguageIcon.getParent(), SWT.DROP_DOWN);
+        mLanguage.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mLanguage.addVerifyListener(new LanguageRegionVerifier());
+        mLanguage.addSelectionListener(new SelectionListener() {
+            public void widgetDefaultSelected(SelectionEvent e) {
+                onLanguageChange();
+            }
+            public void widgetSelected(SelectionEvent e) {
+                onLanguageChange();
+            }
+        });
+        mLanguage.addModifyListener(new ModifyListener() {
+            public void modifyText(ModifyEvent e) {
+                onLanguageChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Region");
+        mRegionIcon = createControlComposite(this, true /* grab_horizontal */);
+        mRegion = new Combo(mRegionIcon.getParent(), SWT.DROP_DOWN);
+        mRegion.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mRegion.addVerifyListener(new LanguageRegionVerifier());
+        mRegion.addSelectionListener(new SelectionListener() {
+            public void widgetDefaultSelected(SelectionEvent e) {
+                onRegionChange();
+            }
+            public void widgetSelected(SelectionEvent e) {
+                onRegionChange();
+            }
+        });
+        mRegion.addModifyListener(new ModifyListener() {
+            public void modifyText(ModifyEvent e) {
+                onRegionChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Orient");
+        mOrientationIcon = createControlComposite(this, true /* grab_horizontal */);
+        mOrientation = new Combo(mOrientationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
+        ScreenOrientation[] soValues = ScreenOrientation.values();
+        mOrientation.add("(Default)");
+        for (ScreenOrientation value : soValues) {
+            mOrientation.add(value.getDisplayValue());
+        }
+        mOrientation.select(0);
+        mOrientation.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mOrientation.addSelectionListener(new SelectionAdapter() {
+           @Override
+            public void widgetSelected(SelectionEvent e) {
+               onOrientationChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Density");
+        mDensityIcon = createControlComposite(this, true /* grab_horizontal */);
+        mDensity = new Combo(mDensityIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
+        Density[] dValues = Density.values();
+        mDensity.add("(Default)");
+        for (Density value : dValues) {
+            mDensity.add(value.getDisplayValue());
+        }
+        mDensity.select(0);
+        mDensity.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mDensity.addSelectionListener(new SelectionAdapter() {
+           @Override
+            public void widgetSelected(SelectionEvent e) {
+               onDensityChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Touch");
+        mTouchIcon = createControlComposite(this, true /* grab_horizontal */);
+        mTouch = new Combo(mTouchIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
+        TouchScreenType[] tstValues = TouchScreenType.values();
+        mTouch.add("(Default)");
+        for (TouchScreenType value : tstValues) {
+            mTouch.add(value.getDisplayValue());
+        }
+        mTouch.select(0);
+        mTouch.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mTouch.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                onTouchChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Keybrd");
+        mKeyboardIcon = createControlComposite(this, true /* grab_horizontal */);
+        mKeyboard = new Combo(mKeyboardIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
+        KeyboardState[] ksValues = KeyboardState.values();
+        mKeyboard.add("(Default)");
+        for (KeyboardState value : ksValues) {
+            mKeyboard.add(value.getDisplayValue());
+        }
+        mKeyboard.select(0);
+        mKeyboard.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mKeyboard.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                onKeyboardChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Input");
+        mTextInputIcon = createControlComposite(this, true /* grab_horizontal */);
+        mTextInput = new Combo(mTextInputIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
+        TextInputMethod[] timValues = TextInputMethod.values();
+        mTextInput.add("(Default)");
+        for (TextInputMethod value : timValues) {
+            mTextInput.add(value.getDisplayValue());
+        }
+        mTextInput.select(0);
+        mTextInput.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mTextInput.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                onTextInputChange();
+            }
+        });
+
+        new Label(this, SWT.NONE).setText("Nav");
+        mNavigationIcon = createControlComposite(this, true /* grab_horizontal */);
+        mNavigation = new Combo(mNavigationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY);
+        NavigationMethod[] nValues = NavigationMethod.values();
+        mNavigation.add("(Default)");
+        for (NavigationMethod value : nValues) {
+            mNavigation.add(value.getDisplayValue());
+        }
+        mNavigation.select(0);
+        mNavigation.setLayoutData(new GridData(
+                GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        mNavigation.addSelectionListener(new SelectionAdapter() {
+            @Override
+             public void widgetSelected(SelectionEvent e) {
+                onNavigationChange();
+            }
+        });
+
+        Composite labelParent = new Composite(this, SWT.NONE);
+        labelParent.setLayout(gl = new GridLayout(8, false));
+        gl.marginWidth = gl.marginHeight = 0;
+        labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
+        gd.horizontalSpan = cols;
+
+        new Label(labelParent, SWT.NONE).setText("Editing config:");
+        mCurrentLayoutLabel = new Label(labelParent, SWT.NONE);
+        mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
+        gd.widthHint = 50;
+
+        new Label(labelParent, SWT.NONE).setText("Size");
+        mSizeIcon = createControlComposite(labelParent, false);
+        Composite sizeParent = new Composite(mSizeIcon.getParent(), SWT.NONE);
+        sizeParent.setLayout(gl = new GridLayout(3, false));
+        gl.marginWidth = gl.marginHeight = 0;
+        gl.horizontalSpacing = 0;
+
+        mSize1 = new Text(sizeParent, SWT.BORDER);
+        mSize1.setLayoutData(gd = new GridData());
+        gd.widthHint = 30;
+        new Label(sizeParent, SWT.NONE).setText("x");
+        mSize2 = new Text(sizeParent, SWT.BORDER);
+        mSize2.setLayoutData(gd = new GridData());
+        gd.widthHint = 30;
+
+        DimensionVerifier verifier = new DimensionVerifier();
+        mSize1.addVerifyListener(verifier);
+        mSize2.addVerifyListener(verifier);
+
+        SelectionListener sl = new SelectionListener() {
+            public void widgetDefaultSelected(SelectionEvent e) {
+                onSizeChange();
+            }
+            public void widgetSelected(SelectionEvent e) {
+                onSizeChange();
+            }
+        };
+
+        mSize1.addSelectionListener(sl);
+        mSize2.addSelectionListener(sl);
+
+        ModifyListener sizeModifyListener = new ModifyListener() {
+            public void modifyText(ModifyEvent e) {
+                onSizeChange();
+            }
+        };
+
+        mSize1.addModifyListener(sizeModifyListener);
+        mSize2.addModifyListener(sizeModifyListener);
+
+        // first separator
+        Label separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL);
+        separator.setLayoutData(gd = new GridData(
+                GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
+        gd.heightHint = 0;
+
+        mThemeCombo = new Combo(labelParent, SWT.READ_ONLY | SWT.DROP_DOWN);
+        mThemeCombo.setEnabled(false);
+        updateUIFromResources();
+        mThemeCombo.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                onThemeChange();
+            }
+        });
+
+        // second separator
+        separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL);
+        separator.setLayoutData(gd = new GridData(
+                GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL));
+        gd.heightHint = 0;
+
+        mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT);
+        mCreateButton.setText("Create...");
+        mCreateButton.setEnabled(false);
+        mCreateButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                if (mListener != null) {
+                    mListener.onCreate();
+                }
+            }
+        });
+
+    }
+
+    public void setConfig(FolderConfiguration config) {
+        mCurrentConfig.set(config);
+    }
+
+    public FolderConfiguration getCurrentConfig() {
+        return mCurrentConfig;
+    }
+
+    public void getCurrentConfig(FolderConfiguration config) {
+        config.set(mCurrentConfig);
+    }
+
+    public Rectangle getScreenBounds() {
+        ScreenOrientation orientation = null;
+        if (mOrientation.getSelectionIndex() == 0) {
+            orientation = ScreenOrientation.PORTRAIT;
+        } else {
+            orientation = ScreenOrientation.getByIndex(
+                    mOrientation.getSelectionIndex() - 1);
+        }
+
+        int s1, s2;
+
+        // get the size from the UI controls. If it fails, revert to default values.
+        try {
+            s1 = Integer.parseInt(mSize1.getText().trim());
+        } catch (NumberFormatException e) {
+            s1 = 480;
+        }
+
+        try {
+            s2 = Integer.parseInt(mSize2.getText().trim());
+        } catch (NumberFormatException e) {
+            s2 = 320;
+        }
+
+        // make sure s1 is bigger than s2
+        if (s1 < s2) {
+            int tmp = s1;
+            s1 = s2;
+            s2 = tmp;
+        }
+
+        switch (orientation) {
+            default:
+            case PORTRAIT:
+                return new Rectangle(0, 0, s2, s1);
+            case LANDSCAPE:
+                return new Rectangle(0, 0, s1, s2);
+            case SQUARE:
+                return new Rectangle(0, 0, s1, s1);
+        }
+    }
+
+    /**
+     * Updates the UI from values in the resources, such as languages, regions, themes, etc...
+     * This must be called from the UI thread.
+     */
+    public void updateUIFromResources() {
+        if (mListener == null) {
+            return; // can't do anything w/o it.
+        }
+
+        ProjectResources frameworkProject = mListener.getFrameworkResources();
+
+        mDisableUpdates = true;
+
+        // Reset stuff
+        int selection = mThemeCombo.getSelectionIndex();
+        mThemeCombo.removeAll();
+        mPlatformThemeCount = 0;
+        mLanguage.removeAll();
+
+        Set<String> languages = new HashSet<String>();
+        ArrayList<String> themes = new ArrayList<String>();
+
+        // get the themes, and languages from the Framework.
+        if (frameworkProject != null) {
+            // get the configured resources for the framework
+            Map<String, Map<String, IResourceValue>> frameworResources =
+                mListener.getConfiguredFrameworkResources();
+
+            if (frameworResources != null) {
+                // get the styles.
+                Map<String, IResourceValue> styles = frameworResources.get(
+                        ResourceType.STYLE.getName());
+
+
+                // collect the themes out of all the styles.
+                for (IResourceValue value : styles.values()) {
+                    String name = value.getName();
+                    if (name.startsWith("Theme.") || name.equals("Theme")) {
+                        themes.add(value.getName());
+                        mPlatformThemeCount++;
+                    }
+                }
+
+                // sort them and add them to the combo
+                Collections.sort(themes);
+
+                for (String theme : themes) {
+                    mThemeCombo.add(theme);
+                }
+
+                mPlatformThemeCount = themes.size();
+                themes.clear();
+            }
+            // now get the languages from the framework.
+            Set<String> frameworkLanguages = frameworkProject.getLanguages();
+            if (frameworkLanguages != null) {
+                languages.addAll(frameworkLanguages);
+            }
+        }
+
+        // now get the themes and languages from the project.
+        ProjectResources project = mListener.getProjectResources();
+        // in cases where the opened file is not linked to a project, this could be null.
+        if (project != null) {
+            // get the configured resources for the project
+            Map<String, Map<String, IResourceValue>> configuredProjectRes =
+                mListener.getConfiguredProjectResources();
+
+            if (configuredProjectRes != null) {
+                // get the styles.
+                Map<String, IResourceValue> styleMap = configuredProjectRes.get(
+                        ResourceType.STYLE.getName());
+
+                if (styleMap != null) {
+                    // collect the themes out of all the styles, ie styles that extend,
+                    // directly or indirectly a platform theme.
+                    for (IResourceValue value : styleMap.values()) {
+                        if (isTheme(value, styleMap)) {
+                            themes.add(value.getName());
+                        }
+                    }
+
+                    // sort them and add them the to the combo.
+                    if (mPlatformThemeCount > 0 && themes.size() > 0) {
+                        mThemeCombo.add(THEME_SEPARATOR);
+                    }
+
+                    Collections.sort(themes);
+
+                    for (String theme : themes) {
+                        mThemeCombo.add(theme);
+                    }
+                }
+            }
+
+            // now get the languages from the project.
+            Set<String> projectLanguages = project.getLanguages();
+            if (projectLanguages != null) {
+                languages.addAll(projectLanguages);
+            }
+        }
+
+        // add the languages to the Combo
+        for (String language : languages) {
+            mLanguage.add(language);
+        }
+
+        mDisableUpdates = false;
+
+        // and update the Region UI based on the current language
+        updateRegionUi();
+
+        // handle default selection of themes
+        if (mThemeCombo.getItemCount() > 0) {
+            mThemeCombo.setEnabled(true);
+            if (selection == -1) {
+                selection = 0;
+            }
+
+            if (mThemeCombo.getItemCount() <= selection) {
+                mThemeCombo.select(0);
+            } else {
+                mThemeCombo.select(selection);
+            }
+        } else {
+            mThemeCombo.setEnabled(false);
+        }
+
+        mThemeCombo.getParent().layout();
+    }
+
+    /**
+     * Returns the current theme, or null if the combo has no selection.
+     */
+    public String getTheme() {
+        int themeIndex = mThemeCombo.getSelectionIndex();
+        if (themeIndex != -1) {
+            return mThemeCombo.getItem(themeIndex);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns whether the current theme selection is a project theme.
+     * <p/>The returned value is meaningless if {@link #getTheme()} returns <code>null</code>.
+     * @return true for project theme, false for framework theme
+     */
+    public boolean isProjectTheme() {
+        return mThemeCombo.getSelectionIndex() >= mPlatformThemeCount;
+    }
+
+
+    public void setEnabledCreate(boolean enabled) {
+        mCreateButton.setEnabled(enabled);
+    }
+
+    /**
+     * Update the UI controls state with a given {@link FolderConfiguration}.
+     * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
+     * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
+     * the UI control is not modified. However if the value in the control is not the default value,
+     * a warning icon is shown.
+     * @param config The {@link FolderConfiguration} to set.
+     * @param force Whether the UI should be changed to exactly match the received configuration.
+     */
+    public void setConfiguration(FolderConfiguration config, boolean force) {
+        mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets.
+
+        mCountryIcon.setImage(mMatchImage);
+        CountryCodeQualifier countryQualifier = config.getCountryCodeQualifier();
+        if (countryQualifier != null) {
+            mCountry.setText(String.format("%1$d", countryQualifier.getCode()));
+            mCurrentConfig.setCountryCodeQualifier(countryQualifier);
+        } else if (force) {
+            mCountry.setText(""); //$NON-NLS-1$
+            mCurrentConfig.setCountryCodeQualifier(null);
+        } else if (mCountry.getText().length() > 0) {
+            mCountryIcon.setImage(mWarningImage);
+        }
+
+        mNetworkIcon.setImage(mMatchImage);
+        NetworkCodeQualifier networkQualifier = config.getNetworkCodeQualifier();
+        if (networkQualifier != null) {
+            mNetwork.setText(String.format("%1$d", networkQualifier.getCode()));
+            mCurrentConfig.setNetworkCodeQualifier(networkQualifier);
+        } else if (force) {
+            mNetwork.setText(""); //$NON-NLS-1$
+            mCurrentConfig.setNetworkCodeQualifier(null);
+        } else if (mNetwork.getText().length() > 0) {
+            mNetworkIcon.setImage(mWarningImage);
+        }
+
+        mLanguageIcon.setImage(mMatchImage);
+        LanguageQualifier languageQualifier = config.getLanguageQualifier();
+        if (languageQualifier != null) {
+            mLanguage.setText(languageQualifier.getValue());
+            mCurrentConfig.setLanguageQualifier(languageQualifier);
+        } else if (force) {
+            mLanguage.setText(""); //$NON-NLS-1$
+            mCurrentConfig.setLanguageQualifier(null);
+        } else if (mLanguage.getText().length() > 0) {
+            mLanguageIcon.setImage(mWarningImage);
+        }
+
+        mRegionIcon.setImage(mMatchImage);
+        RegionQualifier regionQualifier = config.getRegionQualifier();
+        if (regionQualifier != null) {
+            mRegion.setText(regionQualifier.getValue());
+            mCurrentConfig.setRegionQualifier(regionQualifier);
+        } else if (force) {
+            mRegion.setText(""); //$NON-NLS-1$
+            mCurrentConfig.setRegionQualifier(null);
+        } else if (mRegion.getText().length() > 0) {
+            mRegionIcon.setImage(mWarningImage);
+        }
+
+        mOrientationIcon.setImage(mMatchImage);
+        ScreenOrientationQualifier orientationQualifier = config.getScreenOrientationQualifier();
+        if (orientationQualifier != null) {
+            mOrientation.select(
+                    ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1);
+            mCurrentConfig.setScreenOrientationQualifier(orientationQualifier);
+        } else if (force) {
+            mOrientation.select(0);
+            mCurrentConfig.setScreenOrientationQualifier(null);
+        } else if (mOrientation.getSelectionIndex() != 0) {
+            mOrientationIcon.setImage(mWarningImage);
+        }
+
+        mDensityIcon.setImage(mMatchImage);
+        PixelDensityQualifier densityQualifier = config.getPixelDensityQualifier();
+        if (densityQualifier != null) {
+            mDensity.select(
+                    Density.getIndex(densityQualifier.getValue()) + 1);
+            mCurrentConfig.setPixelDensityQualifier(densityQualifier);
+        } else if (force) {
+            mDensity.select(0);
+            mCurrentConfig.setPixelDensityQualifier(null);
+        } else if (mDensity.getSelectionIndex() != 0) {
+            mDensityIcon.setImage(mWarningImage);
+        }
+
+        mTouchIcon.setImage(mMatchImage);
+        TouchScreenQualifier touchQualifier = config.getTouchTypeQualifier();
+        if (touchQualifier != null) {
+            mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1);
+            mCurrentConfig.setTouchTypeQualifier(touchQualifier);
+        } else if (force) {
+            mTouch.select(0);
+            mCurrentConfig.setTouchTypeQualifier(null);
+        } else if (mTouch.getSelectionIndex() != 0) {
+            mTouchIcon.setImage(mWarningImage);
+        }
+
+        mKeyboardIcon.setImage(mMatchImage);
+        KeyboardStateQualifier keyboardQualifier = config.getKeyboardStateQualifier();
+        if (keyboardQualifier != null) {
+            mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1);
+            mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier);
+        } else if (force) {
+            mKeyboard.select(0);
+            mCurrentConfig.setKeyboardStateQualifier(null);
+        } else if (mKeyboard.getSelectionIndex() != 0) {
+            mKeyboardIcon.setImage(mWarningImage);
+        }
+
+        mTextInputIcon.setImage(mMatchImage);
+        TextInputMethodQualifier inputQualifier = config.getTextInputMethodQualifier();
+        if (inputQualifier != null) {
+            mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1);
+            mCurrentConfig.setTextInputMethodQualifier(inputQualifier);
+        } else if (force) {
+            mTextInput.select(0);
+            mCurrentConfig.setTextInputMethodQualifier(null);
+        } else if (mTextInput.getSelectionIndex() != 0) {
+            mTextInputIcon.setImage(mWarningImage);
+        }
+
+        mNavigationIcon.setImage(mMatchImage);
+        NavigationMethodQualifier navigationQualifiter = config.getNavigationMethodQualifier();
+        if (navigationQualifiter != null) {
+            mNavigation.select(
+                    NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1);
+            mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter);
+        } else if (force) {
+            mNavigation.select(0);
+            mCurrentConfig.setNavigationMethodQualifier(null);
+        } else if (mNavigation.getSelectionIndex() != 0) {
+            mNavigationIcon.setImage(mWarningImage);
+        }
+
+        mSizeIcon.setImage(mMatchImage);
+        ScreenDimensionQualifier sizeQualifier = config.getScreenDimensionQualifier();
+        if (sizeQualifier != null) {
+            mSize1.setText(String.format("%1$d", sizeQualifier.getValue1()));
+            mSize2.setText(String.format("%1$d", sizeQualifier.getValue2()));
+            mCurrentConfig.setScreenDimensionQualifier(sizeQualifier);
+        } else if (force) {
+            mSize1.setText(""); //$NON-NLS-1$
+            mSize2.setText(""); //$NON-NLS-1$
+            mCurrentConfig.setScreenDimensionQualifier(null);
+        } else if (mSize1.getText().length() > 0 && mSize2.getText().length() > 0) {
+            mSizeIcon.setImage(mWarningImage);
+        }
+
+        // update the string showing the folder name
+        String current = config.toDisplayString();
+        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
+
+        mDisableUpdates = false;
+    }
+
+    /**
+     * Displays an error icon in front of all the non-null qualifiers.
+     */
+    public void displayConfigError() {
+        mCountryIcon.setImage(mMatchImage);
+        CountryCodeQualifier countryQualifier = mCurrentConfig.getCountryCodeQualifier();
+        if (countryQualifier != null) {
+            mCountryIcon.setImage(mErrorImage);
+        }
+
+        mNetworkIcon.setImage(mMatchImage);
+        NetworkCodeQualifier networkQualifier = mCurrentConfig.getNetworkCodeQualifier();
+        if (networkQualifier != null) {
+            mNetworkIcon.setImage(mErrorImage);
+        }
+
+        mLanguageIcon.setImage(mMatchImage);
+        LanguageQualifier languageQualifier = mCurrentConfig.getLanguageQualifier();
+        if (languageQualifier != null) {
+            mLanguageIcon.setImage(mErrorImage);
+        }
+
+        mRegionIcon.setImage(mMatchImage);
+        RegionQualifier regionQualifier = mCurrentConfig.getRegionQualifier();
+        if (regionQualifier != null) {
+            mRegionIcon.setImage(mErrorImage);
+        }
+
+        mOrientationIcon.setImage(mMatchImage);
+        ScreenOrientationQualifier orientationQualifier =
+            mCurrentConfig.getScreenOrientationQualifier();
+        if (orientationQualifier != null) {
+            mOrientationIcon.setImage(mErrorImage);
+        }
+
+        mDensityIcon.setImage(mMatchImage);
+        PixelDensityQualifier densityQualifier = mCurrentConfig.getPixelDensityQualifier();
+        if (densityQualifier != null) {
+            mDensityIcon.setImage(mErrorImage);
+        }
+
+        mTouchIcon.setImage(mMatchImage);
+        TouchScreenQualifier touchQualifier = mCurrentConfig.getTouchTypeQualifier();
+        if (touchQualifier != null) {
+            mTouchIcon.setImage(mErrorImage);
+        }
+
+        mKeyboardIcon.setImage(mMatchImage);
+        KeyboardStateQualifier keyboardQualifier = mCurrentConfig.getKeyboardStateQualifier();
+        if (keyboardQualifier != null) {
+            mKeyboardIcon.setImage(mErrorImage);
+        }
+
+        mTextInputIcon.setImage(mMatchImage);
+        TextInputMethodQualifier inputQualifier = mCurrentConfig.getTextInputMethodQualifier();
+        if (inputQualifier != null) {
+            mTextInputIcon.setImage(mErrorImage);
+        }
+
+        mNavigationIcon.setImage(mMatchImage);
+        NavigationMethodQualifier navigationQualifiter =
+            mCurrentConfig.getNavigationMethodQualifier();
+        if (navigationQualifiter != null) {
+            mNavigationIcon.setImage(mErrorImage);
+        }
+
+        mSizeIcon.setImage(mMatchImage);
+        ScreenDimensionQualifier sizeQualifier = mCurrentConfig.getScreenDimensionQualifier();
+        if (sizeQualifier != null) {
+            mSizeIcon.setImage(mErrorImage);
+        }
+
+        // update the string showing the folder name
+        String current = mCurrentConfig.toDisplayString();
+        mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
+    }
+
+
+
+    private void onCountryCodeChange() {
+        // because mCountry triggers onCountryCodeChange at each modification, calling setText()
+        // will trigger notifications, and we don't want that.
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        // update the current config
+        String value = mCountry.getText();
+
+        // empty string, means no qualifier.
+        if (value.length() == 0) {
+            mCurrentConfig.setCountryCodeQualifier(null);
+        } else {
+            try {
+                CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier(
+                        CountryCodeQualifier.getFolderSegment(Integer.parseInt(value)));
+                if (qualifier != null) {
+                    mCurrentConfig.setCountryCodeQualifier(qualifier);
+                } else {
+                    // Failure! Looks like the value is wrong (for instance a one letter string).
+                    // We do nothing in this case.
+                    mCountryIcon.setImage(mErrorImage);
+                    return;
+                }
+            } catch (NumberFormatException e) {
+                // Looks like the code is not a number. This should not happen since the text
+                // field has a VerifyListener that prevents it.
+                mCurrentConfig.setCountryCodeQualifier(null);
+                mCountryIcon.setImage(mErrorImage);
+            }
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onNetworkCodeChange() {
+        // because mNetwork triggers onNetworkCodeChange at each modification, calling setText()
+        // will trigger notifications, and we don't want that.
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        // update the current config
+        String value = mNetwork.getText();
+
+        // empty string, means no qualifier.
+        if (value.length() == 0) {
+            mCurrentConfig.setNetworkCodeQualifier(null);
+        } else {
+            try {
+                NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier(
+                        NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value)));
+                if (qualifier != null) {
+                    mCurrentConfig.setNetworkCodeQualifier(qualifier);
+                } else {
+                    // Failure! Looks like the value is wrong (for instance a one letter string).
+                    // We do nothing in this case.
+                    mNetworkIcon.setImage(mErrorImage);
+                    return;
+                }
+            } catch (NumberFormatException e) {
+                // Looks like the code is not a number. This should not happen since the text
+                // field has a VerifyListener that prevents it.
+                mCurrentConfig.setNetworkCodeQualifier(null);
+                mNetworkIcon.setImage(mErrorImage);
+            }
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    /**
+     * Call back for language combo selection
+     */
+    private void onLanguageChange() {
+        // because mLanguage triggers onLanguageChange at each modification, the filling
+        // of the combo with data will trigger notifications, and we don't want that.
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        // update the current config
+        String value = mLanguage.getText();
+
+        updateRegionUi();
+
+        // empty string, means no qualifier.
+        if (value.length() == 0) {
+            mCurrentConfig.setLanguageQualifier(null);
+        } else {
+            LanguageQualifier qualifier = null;
+            String segment = LanguageQualifier.getFolderSegment(value);
+            if (segment != null) {
+                qualifier = LanguageQualifier.getQualifier(segment);
+            }
+
+            if (qualifier != null) {
+                mCurrentConfig.setLanguageQualifier(qualifier);
+            } else {
+                // Failure! Looks like the value is wrong (for instance a one letter string).
+                mCurrentConfig.setLanguageQualifier(null);
+                mLanguageIcon.setImage(mErrorImage);
+            }
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onRegionChange() {
+        // because mRegion triggers onRegionChange at each modification, the filling
+        // of the combo with data will trigger notifications, and we don't want that.
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        // update the current config
+        String value = mRegion.getText();
+
+        // empty string, means no qualifier.
+        if (value.length() == 0) {
+            mCurrentConfig.setRegionQualifier(null);
+        } else {
+            RegionQualifier qualifier = null;
+            String segment = RegionQualifier.getFolderSegment(value);
+            if (segment != null) {
+                qualifier = RegionQualifier.getQualifier(segment);
+            }
+
+            if (qualifier != null) {
+                mCurrentConfig.setRegionQualifier(qualifier);
+            } else {
+                // Failure! Looks like the value is wrong (for instance a one letter string).
+                mCurrentConfig.setRegionQualifier(null);
+                mRegionIcon.setImage(mErrorImage);
+            }
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onOrientationChange() {
+        // update the current config
+        int index = mOrientation.getSelectionIndex();
+        if (index != 0) {
+            mCurrentConfig.setScreenOrientationQualifier(new ScreenOrientationQualifier(
+                ScreenOrientation.getByIndex(index-1)));
+        } else {
+            mCurrentConfig.setScreenOrientationQualifier(null);
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onDensityChange() {
+        int index = mDensity.getSelectionIndex();
+        if (index != 0) {
+            mCurrentConfig.setPixelDensityQualifier((new PixelDensityQualifier(
+                Density.getByIndex(index-1))));
+        } else {
+            mCurrentConfig.setPixelDensityQualifier(null);
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onTouchChange() {
+        // update the current config
+        int index = mTouch.getSelectionIndex();
+        if (index != 0) {
+            mCurrentConfig.setTouchTypeQualifier(new TouchScreenQualifier(
+                TouchScreenType.getByIndex(index-1)));
+        } else {
+            mCurrentConfig.setTouchTypeQualifier(null);
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onKeyboardChange() {
+        // update the current config
+        int index = mKeyboard.getSelectionIndex();
+        if (index != 0) {
+            mCurrentConfig.setKeyboardStateQualifier(new KeyboardStateQualifier(
+                KeyboardState.getByIndex(index-1)));
+        } else {
+            mCurrentConfig.setKeyboardStateQualifier(null);
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onTextInputChange() {
+        // update the current config
+        int index = mTextInput.getSelectionIndex();
+        if (index != 0) {
+            mCurrentConfig.setTextInputMethodQualifier(new TextInputMethodQualifier(
+                TextInputMethod.getByIndex(index-1)));
+        } else {
+            mCurrentConfig.setTextInputMethodQualifier(null);
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onNavigationChange() {
+        // update the current config
+        int index = mNavigation.getSelectionIndex();
+        if (index != 0) {
+            mCurrentConfig.setNavigationMethodQualifier(new NavigationMethodQualifier(
+                NavigationMethod.getByIndex(index-1)));
+        } else {
+            mCurrentConfig.setNavigationMethodQualifier(null);
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onSizeChange() {
+        // because mSize1 and mSize2 trigger onSizeChange at each modification, calling setText()
+        // will trigger notifications, and we don't want that.
+        if (mDisableUpdates == true) {
+            return;
+        }
+
+        // update the current config
+        String size1 = mSize1.getText();
+        String size2 = mSize2.getText();
+
+        // if only one of the strings is empty, do nothing
+        if ((size1.length() == 0) ^ (size2.length() == 0)) {
+            mSizeIcon.setImage(mErrorImage);
+            return;
+        } else if (size1.length() == 0 && size2.length() == 0) {
+            // both sizes are empty: remove the qualifier.
+            mCurrentConfig.setScreenDimensionQualifier(null);
+        } else {
+            ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1,
+                    size2);
+
+            if (qualifier != null) {
+                mCurrentConfig.setScreenDimensionQualifier(qualifier);
+            } else {
+                // Failure! Looks like the value is wrong.
+                // we do nothing in this case.
+                return;
+            }
+        }
+
+        if (mListener != null) {
+            mListener.onConfigurationChange();
+        }
+    }
+
+    private void onThemeChange() {
+        int themeIndex = mThemeCombo.getSelectionIndex();
+        if (themeIndex != -1) {
+            String theme = mThemeCombo.getItem(themeIndex);
+
+            if (theme.equals(THEME_SEPARATOR)) {
+                mThemeCombo.select(0);
+            }
+
+            if (mListener != null) {
+                mListener.onThemeChange();
+            }
+        }
+    }
+
+    /**
+     * Creates a composite with no margin/spacing, and puts a {@link Label} in it with the matching
+     * icon.
+     * @param parent the parent to receive the composite
+     * @return the created {@link Label} object.
+     */
+    private Label createControlComposite(Composite parent, boolean grab) {
+        GridLayout gl;
+
+        Composite composite = new Composite(parent, SWT.NONE);
+        composite.setLayout(gl = new GridLayout(2, false));
+        gl.marginHeight = gl.marginWidth = 0;
+        gl.horizontalSpacing = 0;
+        if (grab) {
+            composite.setLayoutData(
+                    new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
+        }
+
+        // create the label
+        Label icon = new Label(composite, SWT.NONE);
+        icon.setImage(mMatchImage);
+
+        return icon;
+    }
+
+    /**
+     * Update the Region UI widget based on the current language selection
+     * @param projectResources the project resources or {@code null}.
+     * @param frameworkResources the framework resource or {@code null}
+     */
+    private void updateRegionUi() {
+        if (mListener == null) {
+            return;
+        }
+
+        ProjectResources projectResources = mListener.getProjectResources();
+        ProjectResources frameworkResources = mListener.getFrameworkResources();
+
+        String currentLanguage = mLanguage.getText();
+
+        Set<String> set = null;
+
+        if (projectResources != null) {
+            set = projectResources.getRegions(currentLanguage);
+        }
+
+        if (frameworkResources != null) {
+            if (set != null) {
+                Set<String> set2 = frameworkResources.getRegions(currentLanguage);
+                set.addAll(set2);
+            } else {
+                set = frameworkResources.getRegions(currentLanguage);
+            }
+        }
+
+        if (set != null) {
+            mDisableUpdates = true;
+
+            mRegion.removeAll();
+            for (String region : set) {
+                mRegion.add(region);
+            }
+
+            mDisableUpdates = false;
+        }
+    }
+
+
+    /**
+     * Returns whether the given <var>style</var> is a theme.
+     * This is done by making sure the parent is a theme.
+     * @param value the style to check
+     * @param styleMap the map of styles for the current project. Key is the style name.
+     * @return True if the given <var>style</var> is a theme.
+     */
+    private boolean isTheme(IResourceValue value, Map<String, IResourceValue> styleMap) {
+        if (value instanceof IStyleResourceValue) {
+            IStyleResourceValue style = (IStyleResourceValue)value;
+
+            boolean frameworkStyle = false;
+            String parentStyle = style.getParentStyle();
+            if (parentStyle == null) {
+                // if there is no specified parent style we look an implied one.
+                // For instance 'Theme.light' is implied child style of 'Theme',
+                // and 'Theme.light.fullscreen' is implied child style of 'Theme.light'
+                String name = style.getName();
+                int index = name.lastIndexOf('.');
+                if (index != -1) {
+                    parentStyle = name.substring(0, index);
+                }
+            } else {
+                // remove the useless @ if it's there
+                if (parentStyle.startsWith("@")) {
+                    parentStyle = parentStyle.substring(1);
+                }
+
+                // check for framework identifier.
+                if (parentStyle.startsWith("android:")) {
+                    frameworkStyle = true;
+                    parentStyle = parentStyle.substring("android:".length());
+                }
+
+                // at this point we could have the format style/<name>. we want only the name
+                if (parentStyle.startsWith("style/")) {
+                    parentStyle = parentStyle.substring("style/".length());
+                }
+            }
+
+            if (frameworkStyle) {
+                // if the parent is a framework style, it has to be 'Theme' or 'Theme.*'
+                return parentStyle.equals("Theme") || parentStyle.startsWith("Theme.");
+            } else {
+                // if it's a project style, we check this is a theme.
+                value = styleMap.get(parentStyle);
+                if (value != null) {
+                    return isTheme(value, styleMap);
+                }
+            }
+        }
+
+        return false;
+    }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.common/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.common/.gitignore
new file mode 100644
index 0000000..d392f0e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.common/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF
index 9f021ae..fd818bf 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: Dalvik Debug Monitor Service
 Bundle-SymbolicName: com.android.ide.eclipse.ddms;singleton:=true
-Bundle-Version: 0.9.3.qualifier
+Bundle-Version: 0.9.4.qualifier
 Bundle-Activator: com.android.ide.eclipse.ddms.DdmsPlugin
 Bundle-Vendor: The Android Open Source Project
 Bundle-Localization: plugin
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.ddms/icons/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/icons/.gitignore
new file mode 100644
index 0000000..f432e88
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/icons/.gitignore
@@ -0,0 +1,31 @@
+add.png
+backward.png
+clear.png
+d.png
+debug-attach.png
+debug-error.png
+debug-wait.png
+delete.png
+device.png
+down.png
+e.png
+edit.png
+empty.png
+emulator.png
+forward.png
+gc.png
+halt.png
+heap.png
+i.png
+importBug.png
+load.png
+pause.png
+play.png
+pull.png
+push.png
+save.png
+thread.png
+up.png
+v.png
+w.png
+warning.png
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.ddms/libs/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/libs/.gitignore
new file mode 100644
index 0000000..d392f0e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/libs/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/.gitignore
new file mode 100644
index 0000000..76d9981
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/.gitignore
@@ -0,0 +1,2 @@
+ddmlib
+ddmuilib
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.editors/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.editors/.gitignore
new file mode 100644
index 0000000..d392f0e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.editors/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/.gitignore b/tools/eclipse/plugins/com.android.ide.eclipse.tests/.gitignore
new file mode 100644
index 0000000..d392f0e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF b/tools/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF
index a93bdef..61e8ee4 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: Android Plugin Tests
 Bundle-SymbolicName: com.android.ide.eclipse.tests
-Bundle-Version: 0.9.3.qualifier
+Bundle-Version: 0.9.4.qualifier
 Bundle-Activator: com.android.ide.eclipse.tests.AndroidTestPlugin
 Require-Bundle: org.eclipse.ui,
  org.eclipse.core.runtime,
diff --git a/tools/eclipse/sites/external/site.xml b/tools/eclipse/sites/external/site.xml
index a00d1a4..ef0df64 100644
--- a/tools/eclipse/sites/external/site.xml
+++ b/tools/eclipse/sites/external/site.xml
@@ -3,10 +3,10 @@
    <description url="https://dl-ssl.google.com/android/eclipse/">
       Update Site for Android Development Toolkit
    </description>
-   <feature url="features/com.android.ide.eclipse.adt_0.9.3.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.3.qualifier">
+   <feature url="features/com.android.ide.eclipse.adt_0.9.4.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.4.qualifier">
       <category name="developer"/>
    </feature>
-   <feature url="features/com.android.ide.eclipse.ddms_0.9.3.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.3.qualifier">
+   <feature url="features/com.android.ide.eclipse.ddms_0.9.4.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.4.qualifier">
       <category name="developer"/>
    </feature>
    <category-def name="developer" label="Developer Tools">
diff --git a/tools/eclipse/sites/internal/site.xml b/tools/eclipse/sites/internal/site.xml
index e19c119..31744d7 100644
--- a/tools/eclipse/sites/internal/site.xml
+++ b/tools/eclipse/sites/internal/site.xml
@@ -3,13 +3,13 @@
    <description url="https://android.corp.google.com/adt/">
       Update Site for Android Development Toolkit
    </description>
-   <feature url="features/com.android.ide.eclipse.adt_0.9.3.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.3.qualifier">
+   <feature url="features/com.android.ide.eclipse.adt_0.9.4.qualifier.jar" id="com.android.ide.eclipse.adt" version="0.9.4.qualifier">
       <category name="developer"/>
    </feature>
-   <feature url="features/com.android.ide.eclipse.ddms_0.9.3.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.3.qualifier">
+   <feature url="features/com.android.ide.eclipse.ddms_0.9.4.qualifier.jar" id="com.android.ide.eclipse.ddms" version="0.9.4.qualifier">
       <category name="developer"/>
    </feature>
-   <feature url="features/com.android.ide.eclipse.tests_0.9.3.qualifier.jar" id="com.android.ide.eclipse.tests" version="0.9.3.qualifier">
+   <feature url="features/com.android.ide.eclipse.tests_0.9.4.qualifier.jar" id="com.android.ide.eclipse.tests" version="0.9.4.qualifier">
       <category name="test"/>
    </feature>
    <category-def name="developer" label="Application Developer Tools">
diff --git a/tools/findunused/findunusedresources b/tools/findunused/findunusedresources
index 748139a..54b1596 100755
--- a/tools/findunused/findunusedresources
+++ b/tools/findunused/findunusedresources
@@ -26,11 +26,15 @@
 for app in $apps
 do
     echo '-----------------------------------------------------------'
+    if [ "$app" == "." ]
+    then
+        app=$(pwd)
+    fi
     if [ -d $app/res ]
     then
         appname=$(basename $app)
         resources=
-        for res in $(echo $app/res/*)
+        for res in $(echo $app/res/* $(find $ANDROID_BUILD_TOP/vendor -type d -wholename $ANDROID_BUILD_TOP/vendor/*/$appname/res | grep overlay))
         do
             resources="$resources $(echo $res | grep -v '\-mcc\|[a-z]*-[a-z][a-z]$\|[a-z]*-[a-z][a-z]-.*')"
         done
@@ -45,19 +49,25 @@
         fi
 
         # find the R.java file that contains all the generated resource identifiers
-        rDotJava=$(find out/target/common/obj/APPS/${appname}_intermediates/ -name R.java)
+        rDotJava=$(find $ANDROID_BUILD_TOP/out/target/common/obj/APPS/${appname}_intermediates/ -name R.java)
 
         # Simplistically process the content of the file to get the names of all the constants,
         # and try to find a reference to each constant.
+
+        # First take all the input files and concatenate them, removing newlines. This allows us to
+        # find expressions that are broken up over multiple lines, i.e. R.drawable.\nsomeconstant
+        find $resources $sources $app/AndroidManifest.xml -type f -print |xargs cat | tr -d '\n ' > /tmp/everything$$
+
+        # Now look for each of the constants in the contatenated file.
         for i in $(cat $rDotJava | grep "\w*=0x\d*" | sed 's/ *public static final int //' | sed 's/=0x.*//')
         do
             # Since periods in the names get translated to underscores in R.java, and you can actually
             # refer to such constants from java by using an underscore instead of a period, we also
             # replace all underscores with a pattern that will match periods and underscores.
             p=$(echo $i | sed 's/_/[\\._]/g')
-            echo $i $(grep -Rw R\\..*\\.$i\\\|@style/$p\\\|@drawable/$p\\\|@anim/$p\\\|@color/$p\\\|@xml/$p\\\|@layout/$p\\\|@menu/$p\\\|@+id/$p\\\|@array/$p\\\|@string/$p\\\|@dimen/$p $resources $sources $app/AndroidManifest.xml | wc -l)
+            echo $i $(grep -cw R\\..*\\.$i\\\|@style/$p\\\|@drawable/$p\\\|@anim/$p\\\|@color/$p\\\|@xml/$p\\\|@layout/$p\\\|@menu/$p\\\|@+id/$p\\\|@array/$p\\\|@string/$p\\\|@dimen/$p\\\|\[a-z\]\*:$p\\\|enumname=\"$p\\\|\<item\>$p\< < /tmp/everything$$)
         done | grep " 0$" | {
-            # this block gets as its input a list of constants which no references were found, one per line
+            # this block gets as its input a list of constants for which no references were found, one per line
             if [ "$showall" == "yes" ]
             then
                 echo $app
@@ -70,5 +80,6 @@
                 fi
             fi
         }
+        rm /tmp/everything$$
     fi
 done
diff --git a/tools/findunused/removeunusedresources b/tools/findunused/removeunusedresources
new file mode 100755
index 0000000..0c38494
--- /dev/null
+++ b/tools/findunused/removeunusedresources
@@ -0,0 +1,84 @@
+#!/bin/bash
+
+if ! which xmlstarlet > /dev/null
+then
+    echo "You need to have the 'xmlstarlet' command in your path"
+    exit
+fi
+
+apps=$1
+CWD=$(pwd)/
+if [ "$apps" = "" ]
+then
+    echo "Please specify the path to an application, or '--all' to process all applications"
+    exit
+elif [ "$apps" = "--all" ]
+then
+    apps=$ANDROID_BUILD_TOP/packages/apps/*
+fi
+
+BASE=$(pwd)/$(dirname $0)
+
+for app in $apps
+do
+    pushd $app
+    $BASE/findunusedresources -p . | {
+        read LINE NUM
+        while [ "$LINE" != "" ]
+        do
+            if [ "Z$LINE" = "Z-----------------------------------------------------------" ]
+            then
+                # skip
+                true
+            elif [ "$LINE" = "$app" ]
+            then
+                # skip
+                true
+            else
+                # try to find the missing resource
+                find res | grep -w $LINE  | {
+                    read RESLINE
+                    while [ "$RESLINE" != "" ]
+                    do
+                        if [ -f $RESLINE ]
+                        then
+                            echo REMOVING FILE: $RESLINE
+                            git rm $RESLINE > /dev/null
+                        else
+                            echo WARNING unexpected result for $LINE
+                        fi
+                        read RESLINE
+                    done
+                }
+                grep -Rwl $LINE res | {
+                    read RESLINE
+                    while [ "$RESLINE" != "" ]
+                    do
+                        ISSTRING=$(echo "$RESLINE" | grep -w "strings\.xml")
+                        if [ -n "$ISSTRING" ]
+                        then
+                            echo REMOVING STRING $LINE from $RESLINE
+                            xmlstarlet ed -P -S -d "/resources/string[@name='$LINE']" $RESLINE > tf$$
+                            mv tf$$ $RESLINE
+                            git add $RESLINE
+                        else
+                            echo REMOVING $LINE from $RESLINE
+                            xmlstarlet ed -P -S -d "/resources/*[@name='$LINE']" $RESLINE > tf$$
+                            mv tf$$ $RESLINE
+                            git add $RESLINE
+                        fi
+                        read RESLINE
+                    done
+                }
+            fi
+            read LINE NUM
+        done
+    }
+    popd
+done
+echo
+echo "Done."
+echo "Please rebuild the updated applications to make sure that everything still builds."
+echo "After rebuilding, rerun 'findunusedresources' or 'removeunusedresources' to see if any more resources are now unused."
+echo "When you're done, you can 'git commit' the change."
+echo
diff --git a/tools/scripts/doc_source.properties b/tools/scripts/doc_source.properties
index 9382836..e6d6915 100644
--- a/tools/scripts/doc_source.properties
+++ b/tools/scripts/doc_source.properties
@@ -1,4 +1,6 @@
 Pkg.UserSrc=false
-Platform.Version=1.6
+Platform.Version=Eclair
 Pkg.Revision=1
 AndroidVersion.ApiLevel=4
+AndroidVersion.CodeName=Eclair
+
diff --git a/tools/scripts/platform_source.properties b/tools/scripts/platform_source.properties
index 49dc3ae..148c364 100644
--- a/tools/scripts/platform_source.properties
+++ b/tools/scripts/platform_source.properties
@@ -1,5 +1,6 @@
-Pkg.Desc=Android SDK Platform 1.6_r1
+Pkg.Desc=Android SDK Platform Eclair
 Pkg.UserSrc=false
-Platform.Version=1.6
+Platform.Version=Eclair
 Pkg.Revision=1
 AndroidVersion.ApiLevel=4
+AndroidVersion.CodeName=Eclair