Merge "Balls test app."
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index c66b2de..556fb10 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -738,6 +738,27 @@
     }
 
     /**
+     * Get the current connection state of the local Bluetooth adapter.
+     * This can be used to check whether the local Bluetooth adapter is connected
+     * to any profile of any other remote Bluetooth Device.
+     *
+     * <p> Use this function along with {@link #ACTION_CONNECTION_STATE_CHANGED}
+     * intent to get the connection state of the adapter.
+     *
+     * @return One of {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTED},
+     * {@link #STATE_CONNECTING} or {@link #STATE_DISCONNECTED}
+     *
+     * @hide
+     */
+    public int getConnectionState() {
+        if (getState() != STATE_ON) return BluetoothAdapter.STATE_DISCONNECTED;
+        try {
+            return mService.getAdapterConnectionState();
+        } catch (RemoteException e) {Log.e(TAG, "getConnectionState:", e);}
+        return BluetoothAdapter.STATE_DISCONNECTED;
+    }
+
+    /**
      * Picks RFCOMM channels until none are left.
      * Avoids reserved channels.
      */
@@ -879,6 +900,7 @@
         return socket;
     }
 
+
     /**
      * Construct an unencrypted, unauthenticated, RFCOMM server socket.
      * Call #accept to retrieve connections to this socket.
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 1eb269d..aefb3f2 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -47,6 +47,8 @@
     boolean isDiscovering();
     byte[] readOutOfBandData();
 
+    int getAdapterConnectionState();
+
     boolean createBond(in String address);
     boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer);
     boolean cancelBondProcess(in String address);
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 6089013..c0743cfe 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1296,7 +1296,7 @@
             mInShowWindow = false;
         }
     }
-    
+
     void showWindowInner(boolean showInput) {
         boolean doShowInput = false;
         boolean wasVisible = mWindowVisible;
@@ -1311,7 +1311,7 @@
         } else {
             showInput = true;
         }
-        
+
         if (DEBUG) Log.v(TAG, "showWindow: updating UI");
         initialize();
         updateFullscreenMode();
@@ -1343,14 +1343,15 @@
         if (doShowInput) {
             startExtractingText(false);
         }
-        
+
         if (!wasVisible) {
             if (DEBUG) Log.v(TAG, "showWindow: showing!");
+            mImm.setIMEButtonVisible(mToken, true);
             onWindowShown();
             mWindow.show();
         }
     }
-    
+
     public void hideWindow() {
         if (mInputViewStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
@@ -1364,11 +1365,12 @@
         if (mWindowVisible) {
             mWindow.hide();
             mWindowVisible = false;
+            mImm.setIMEButtonVisible(mToken, false);
             onWindowHidden();
             mWindowWasVisible = false;
         }
     }
-    
+
     /**
      * Called when the input method window has been shown to the user, after
      * previously not being visible.  This is done after all of the UI setup
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 65047c0..7289012 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -167,6 +167,8 @@
     private static String mDockAddress;
     private String mDockPin;
 
+    private int mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+
     private static class RemoteService {
         public String address;
         public ParcelUuid uuid;
@@ -415,6 +417,7 @@
         mProfilesConnected = 0;
         mProfilesConnecting = 0;
         mProfilesDisconnecting = 0;
+        mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
 
         if (saveSetting) {
             persistBluetoothOnSetting(false);
@@ -2737,11 +2740,18 @@
         }
     }
 
+    public int getAdapterConnectionState() {
+        return mAdapterConnectionState;
+    }
+
     public synchronized void sendConnectionStateChange(BluetoothDevice device, int state,
                                                         int prevState) {
         if (updateCountersAndCheckForConnectionStateChange(device, state, prevState)) {
-            state = getAdapterConnectionState(state);
-            prevState = getAdapterConnectionState(prevState);
+            state = translateToAdapterConnectionState(state);
+            prevState = translateToAdapterConnectionState(prevState);
+
+            mAdapterConnectionState = state;
+
             Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, state);
@@ -2751,7 +2761,7 @@
         }
     }
 
-    private int getAdapterConnectionState(int state) {
+    private int translateToAdapterConnectionState(int state) {
         switch (state) {
             case BluetoothProfile.STATE_CONNECTING:
                 return BluetoothAdapter.STATE_CONNECTING;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7cb6291..033ee7c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -519,6 +519,15 @@
     }
 
     /** @hide */
+    public void setIMEButtonVisible(IBinder imeToken, boolean visible) {
+        try {
+            mService.setIMEButtonVisible(imeToken, visible);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** @hide */
     public void setFullscreenMode(boolean fullScreen) {
         mFullscreenMode = fullScreen;
     }
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index c8e3935..6f6b40b 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -18,11 +18,11 @@
 
 import com.android.internal.R;
 
-import android.app.Activity;
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
 import android.app.ListFragment;
 import android.app.backup.BackupManager;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Bundle;
@@ -45,61 +45,65 @@
         public void onLocaleSelected(Locale locale);
     }
 
-    Loc[] mLocales;
-    String[] mSpecialLocaleCodes;
-    String[] mSpecialLocaleNames;
-
-    private Locale mNewLocale;
-
     LocaleSelectionListener mListener;  // default to null
 
-    private static class Loc implements Comparable<Loc> {
-        static Collator sCollator = Collator.getInstance();
+    public static class LocaleInfo implements Comparable<LocaleInfo> {
+        static final Collator sCollator = Collator.getInstance();
 
         String label;
         Locale locale;
 
-        public Loc(String label, Locale locale) {
+        public LocaleInfo(String label, Locale locale) {
             this.label = label;
             this.locale = locale;
         }
 
+        public String getLabel() {
+            return label;
+        }
+
+        public Locale getLocale() {
+            return locale;
+        }
+
         @Override
         public String toString() {
             return this.label;
         }
 
         @Override
-        public int compareTo(Loc another) {
+        public int compareTo(LocaleInfo another) {
             return sCollator.compare(this.label, another.label);
         }
     }
 
-    private void setUpLocaleList() {
-        final Activity activity = getActivity();
-        final Resources resources = activity.getResources();
-        mSpecialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
-        mSpecialLocaleNames = resources.getStringArray(R.array.special_locale_names);
-
-        final String[] locales = activity.getAssets().getLocales();
+    /**
+     * Constructs an Adapter object containing Locale information. Content is sorted by
+     * {@link LocaleInfo#label}.
+     */
+    public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) {
+        final Resources resources = context.getResources();
+        final String[] locales = context.getAssets().getLocales();
+        final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
+        final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
         Arrays.sort(locales);
         final int origSize = locales.length;
-        Loc[] preprocess = new Loc[origSize];
+        final LocaleInfo[] preprocess = new LocaleInfo[origSize];
         int finalSize = 0;
         for (int i = 0 ; i < origSize; i++ ) {
-            String s = locales[i];
-            int len = s.length();
+            final String s = locales[i];
+            final int len = s.length();
             if (len == 5) {
                 String language = s.substring(0, 2);
                 String country = s.substring(3, 5);
-                Locale l = new Locale(language, country);
+                final Locale l = new Locale(language, country);
 
                 if (finalSize == 0) {
                     if (DEBUG) {
                         Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l)));
                     }
                     preprocess[finalSize++] =
-                            new Loc(toTitleCase(l.getDisplayLanguage(l)), l);
+                            new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l);
                 } else {
                     // check previous entry:
                     //  same lang and a country -> upgrade to full name and
@@ -110,15 +114,20 @@
                         if (DEBUG) {
                             Log.v(TAG, "backing up and fixing "+
                                     preprocess[finalSize-1].label+" to "+
-                                    getDisplayName(preprocess[finalSize-1].locale));
+                                    getDisplayName(preprocess[finalSize-1].locale,
+                                            specialLocaleCodes, specialLocaleNames));
                         }
                         preprocess[finalSize-1].label = toTitleCase(
-                                getDisplayName(preprocess[finalSize-1].locale));
+                                getDisplayName(preprocess[finalSize-1].locale,
+                                        specialLocaleCodes, specialLocaleNames));
                         if (DEBUG) {
-                            Log.v(TAG, "  and adding "+ toTitleCase(getDisplayName(l)));
+                            Log.v(TAG, "  and adding "+ toTitleCase(
+                                    getDisplayName(l, specialLocaleCodes, specialLocaleNames)));
                         }
                         preprocess[finalSize++] =
-                                new Loc(toTitleCase(getDisplayName(l)), l);
+                                new LocaleInfo(toTitleCase(
+                                        getDisplayName(
+                                                l, specialLocaleCodes, specialLocaleNames)), l);
                     } else {
                         String displayName;
                         if (s.equals("zz_ZZ")) {
@@ -129,31 +138,20 @@
                         if (DEBUG) {
                             Log.v(TAG, "adding "+displayName);
                         }
-                        preprocess[finalSize++] = new Loc(displayName, l);
+                        preprocess[finalSize++] = new LocaleInfo(displayName, l);
                     }
                 }
             }
         }
-        mLocales = new Loc[finalSize];
-        for (int i = 0; i < finalSize ; i++) {
-            mLocales[i] = preprocess[i];
+
+        final LocaleInfo[] localeInfos = new LocaleInfo[finalSize];
+        for (int i = 0; i < finalSize; i++) {
+            localeInfos[i] = preprocess[i];
         }
-        Arrays.sort(mLocales);
+        Arrays.sort(localeInfos);
         final int layoutId = R.layout.locale_picker_item;
         final int fieldId = R.id.locale;
-        final ArrayAdapter<Loc> adapter =
-                new ArrayAdapter<Loc>(activity, layoutId, fieldId, mLocales);
-        setListAdapter(adapter);
-    }
-
-    @Override
-    public void onActivityCreated(final Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        setUpLocaleList();
-    }
-
-    public void setLocaleSelectionListener(LocaleSelectionListener listener) {
-        mListener = listener;
+        return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos);
     }
 
     private static String toTitleCase(String s) {
@@ -164,12 +162,13 @@
         return Character.toUpperCase(s.charAt(0)) + s.substring(1);
     }
 
-    private String getDisplayName(Locale l) {
+    private static String getDisplayName(
+            Locale l, String[] specialLocaleCodes, String[] specialLocaleNames) {
         String code = l.toString();
 
-        for (int i = 0; i < mSpecialLocaleCodes.length; i++) {
-            if (mSpecialLocaleCodes[i].equals(code)) {
-                return mSpecialLocaleNames[i];
+        for (int i = 0; i < specialLocaleCodes.length; i++) {
+            if (specialLocaleCodes[i].equals(code)) {
+                return specialLocaleNames[i];
             }
         }
 
@@ -177,6 +176,17 @@
     }
 
     @Override
+    public void onActivityCreated(final Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        final ArrayAdapter<LocaleInfo> adapter = constructAdapter(getActivity());
+        setListAdapter(adapter);
+    }
+
+    public void setLocaleSelectionListener(LocaleSelectionListener listener) {
+        mListener = listener;
+    }
+
+    @Override
     public void onResume() {
         super.onResume();
         getListView().requestFocus();
@@ -191,7 +201,8 @@
     @Override
     public void onListItemClick(ListView l, View v, int position, long id) {
         if (mListener != null) {
-            mListener.onLocaleSelected(mLocales[position].locale);
+            final Locale locale = ((LocaleInfo)getListAdapter().getItem(position)).locale;
+            mListener.onLocaleSelected(locale);
         }
     }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 34a5b11..f87ca3e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -32,5 +32,6 @@
     void animateCollapse();
     void setLightsOn(boolean on);
     void setMenuKeyVisible(boolean visible);
+    void setIMEButtonVisible(boolean visible);
 }
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 90f4d48..d5ae1f0 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -32,6 +32,7 @@
     void removeIcon(String slot);
     void setActiveWindowIsFullscreen(boolean fullscreen);
     void setMenuKeyVisible(boolean visible);
+    void setIMEButtonVisible(boolean visible);
 
     // ---- Methods below are for use by the status bar policy services ----
     // You need the STATUS_BAR_SERVICE permission
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 49ae2bc7..ca1cd59 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -55,6 +55,7 @@
     void hideMySoftInput(in IBinder token, int flags);
     void showMySoftInput(in IBinder token, int flags);
     void updateStatusIcon(in IBinder token, String packageName, int iconId);
+    void setIMEButtonVisible(in IBinder token, boolean visible);
     InputMethodSubtype getCurrentInputMethodSubtype();
     
     boolean setInputMethodEnabled(String id, boolean enabled);
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notification_peek.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notification_peek.xml
new file mode 100644
index 0000000..a7c91f5
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notification_peek.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+-->
+
+<!--    android:background="@drawable/status_bar_closed_default_background" -->
+<com.android.systemui.statusbar.tablet.NotificationPanel
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:background="@*android:drawable/dialog_full_holo_dark"
+    android:orientation="vertical"
+    >
+
+    <FrameLayout 
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal|bottom"
+        android:animationCache="false"
+        android:orientation="vertical"
+        android:background="@drawable/status_bar_background"
+        android:clickable="true"
+        android:focusable="true"
+        android:descendantFocusability="afterDescendants"
+        >
+    </FrameLayout>
+</com.android.systemui.statusbar.tablet.NotificationPanel>
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
index 80c9a49..8cbf3e3 100644
--- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
@@ -23,20 +23,23 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:background="#FF000000"
+    android:background="@*android:drawable/dialog_full_holo_dark"
     android:orientation="vertical"
+    android:animateLayoutChanges="true"
     >
 
     <ScrollView
         android:id="@+id/notificationScroller"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
+        android:animateLayoutChanges="true"
         >
         <LinearLayout 
             android:id="@+id/content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center_horizontal|bottom"
+            android:animateLayoutChanges="true"
             android:animationCache="false"
             android:orientation="vertical"
             android:background="@drawable/status_bar_background"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c164eb4..ed2ed1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -55,6 +55,7 @@
     private static final int MSG_SET_LIGHTS_ON = 0x00070000;
 
     private static final int MSG_SHOW_MENU = 0x00080000;
+    private static final int MSG_SHOW_IME_BUTTON = 0x00090000;
 
     private StatusBarIconList mList;
     private Callbacks mCallbacks;
@@ -81,6 +82,7 @@
         public void animateCollapse();
         public void setLightsOn(boolean on);
         public void setMenuKeyVisible(boolean visible);
+        public void setIMEButtonVisible(boolean visible);
     }
 
     public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
@@ -163,6 +165,13 @@
         }
     }
 
+    public void setIMEButtonVisible(boolean visible) {
+        synchronized (mList) {
+            mHandler.removeMessages(MSG_SHOW_IME_BUTTON);
+            mHandler.obtainMessage(MSG_SHOW_IME_BUTTON, visible ? 1 : 0, 0, null).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         public void handleMessage(Message msg) {
             final int what = msg.what & MSG_MASK;
@@ -223,6 +232,9 @@
                 case MSG_SHOW_MENU:
                     mCallbacks.setMenuKeyVisible(msg.arg1 != 0);
                     break;
+                case MSG_SHOW_IME_BUTTON:
+                    mCallbacks.setIMEButtonVisible(msg.arg1 != 0);
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
index bf58b37..b174973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -1014,6 +1014,7 @@
 
     // Not supported
     public void setMenuKeyVisible(boolean visible) { }
+    public void setIMEButtonVisible(boolean visible) { }
 
     private class Launcher implements View.OnClickListener {
         private PendingIntent mIntent;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
index ae1fdbd..256386b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarService.java
@@ -65,7 +65,7 @@
         mCommandQueue = new CommandQueue(this, iconList);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        boolean[] switches = new boolean[2];
+        boolean[] switches = new boolean[3];
         try {
             mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
                     switches);
@@ -75,6 +75,7 @@
 
         setLightsOn(switches[0]);
         setMenuKeyVisible(switches[1]);
+        setIMEButtonVisible(switches[2]);
 
         // Set up the initial icon state
         int N = iconList.size();
@@ -119,6 +120,7 @@
                    + " icons=" + iconList.size()
                    + " lights=" + (switches[0]?"on":"off")
                    + " menu=" + (switches[1]?"visible":"invisible")
+                   + " imeButton=" + (switches[2]?"visible":"invisible")
                    );
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java
index 56b4f24..c52bd4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodButton.java
@@ -16,7 +16,10 @@
 
 package com.android.systemui.statusbar.tablet;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.provider.Settings;
@@ -32,7 +35,9 @@
 import com.android.server.InputMethodManagerService;
 import com.android.systemui.R;
 
+import java.util.Calendar;
 import java.util.List;
+import java.util.TimeZone;
 
 public class InputMethodButton extends ImageView {
 
@@ -61,8 +66,10 @@
         });
     }
 
+    @Override
     protected void onAttachedToWindow() {
         mIcon = (ImageView) findViewById(R.id.imeButton);
+
         refreshStatusIcon(mKeyboardShown);
     }
 
@@ -127,13 +134,8 @@
         });
     }
 
-    public void showSoftInput() {
-        mKeyboardShown = true;
-        postRefreshStatusIcon();
-    }
-
-    public void hideSoftInput() {
-        mKeyboardShown = false;
+    public void setIMEButtonVisible(boolean visible) {
+        mKeyboardShown = visible;
         postRefreshStatusIcon();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
index 2b4f9d2..df29f95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
@@ -24,6 +24,7 @@
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.ImageView;
+import android.view.MotionEvent;
 
 import com.android.systemui.R;
 
@@ -43,12 +44,12 @@
         public IconLayout(Context context, AttributeSet attrs) {
             super(context, attrs);
         }
-    }
 
-    static class DraggerView extends View {
-        public DraggerView(Context context, AttributeSet attrs) {
-            super(context, attrs);
+        public boolean onInterceptTouchEvent(MotionEvent e) {
+            return true;
         }
     }
 }
 
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
index a01220f..9dc7850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
@@ -18,7 +18,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
 import android.app.ActivityManagerNative;
 import android.app.PendingIntent;
 import android.app.Notification;
@@ -41,6 +44,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -65,6 +69,8 @@
 
     public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
     public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
+    public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
+    public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
     public static final int MSG_OPEN_SYSTEM_PANEL = 1010;
     public static final int MSG_CLOSE_SYSTEM_PANEL = 1011;
     public static final int MSG_OPEN_RECENTS_PANEL = 1020;
@@ -89,8 +95,17 @@
     View mMenuButton;
     View mRecentButton;
 
+    InputMethodButton mInputMethodButton;
+
     NotificationPanel mNotificationPanel;
     SystemPanel mSystemPanel;
+    NotificationPanel mNotificationPeekWindow;
+    ViewGroup mNotificationPeekRow;
+    int mNotificationPeekIndex;
+    LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;
+
+    int mNotificationPeekTapDuration;
+    int mNotificationFlingVelocity;
 
     ViewGroup mPile;
     TextView mClearButton;
@@ -132,7 +147,7 @@
         mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
 
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                400, // ViewGroup.LayoutParams.WRAP_CONTENT,
+                512, // ViewGroup.LayoutParams.WRAP_CONTENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -144,6 +159,41 @@
 
         WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
 
+        // Notification preview window
+        mNotificationPeekWindow = (NotificationPanel) View.inflate(context,
+                R.layout.sysbar_panel_notification_peek, null);
+        mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
+        mNotificationPeekWindow.setVisibility(View.GONE);
+        mNotificationPeekWindow.setOnTouchListener(
+                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPeekWindow));
+        mNotificationPeekScrubRight = new LayoutTransition();
+        mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 
+                ObjectAnimator.ofInt(null, "left", -512, 0));
+        mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 
+                ObjectAnimator.ofInt(null, "left", -512, 0));
+        mNotificationPeekScrubRight.setDuration(500);
+
+        mNotificationPeekScrubLeft = new LayoutTransition();
+        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 
+                ObjectAnimator.ofInt(null, "left", 512, 0));
+        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 
+                ObjectAnimator.ofInt(null, "left", 512, 0));
+        mNotificationPeekScrubLeft.setDuration(500);
+
+        // XXX: setIgnoreChildren?
+        lp = new WindowManager.LayoutParams(
+                512, // ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                PixelFormat.TRANSLUCENT);
+        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+        lp.setTitle("NotificationPeekWindow");
+        lp.windowAnimations = com.android.internal.R.style.Animation_Toast;
+
+        WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);
+
         // System Panel
         mSystemPanel = (SystemPanel) View.inflate(context, R.layout.sysbar_panel_system, null);
         mSystemPanel.setVisibility(View.GONE);
@@ -238,9 +288,13 @@
         mDoNotDisturbButton = (TextView)mNotificationButtons.findViewById(R.id.do_not_disturb);
         mDoNotDisturbButton.setOnClickListener(mOnClickListener);
 
-
         // where the icons go
         mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
+        mIconLayout.setOnTouchListener(new NotificationIconTouchListener());
+
+        ViewConfiguration vc = ViewConfiguration.get(context);
+        mNotificationPeekTapDuration = vc.getTapTimeout();
+        mNotificationFlingVelocity = 300; // px/s
 
         mTicker = new TabletTicker(context, (FrameLayout)sb.findViewById(R.id.ticker));
 
@@ -253,6 +307,9 @@
         mNavigationArea = sb.findViewById(R.id.navigationArea);
         mMenuButton = mNavigationArea.findViewById(R.id.menu);
 
+        // The bar contents buttons
+        mInputMethodButton = (InputMethodButton) mBarContents.findViewById(R.id.imeButton);
+
         // set the initial view visibility
         setAreThereNotifications();
         refreshNotificationTrigger();
@@ -276,9 +333,57 @@
     private class H extends Handler {
         public void handleMessage(Message m) {
             switch (m.what) {
+                case MSG_OPEN_NOTIFICATION_PEEK:
+                    if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
+                    if (m.arg1 >= 0) {
+                        final int N = mNotns.size();
+                        if (mNotificationPeekIndex < N) {
+                            NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
+                            entry.icon.setBackgroundColor(0);
+                        }
+
+                        final int peekIndex = m.arg1;
+                        if (peekIndex < N) {
+                            Slog.d(TAG, "loading peek: " + peekIndex);
+                            NotificationData.Entry entry = mNotns.get(N-1-peekIndex);
+                            NotificationData.Entry copy = new NotificationData.Entry(
+                                    entry.key, 
+                                    entry.notification, 
+                                    entry.icon);
+                            inflateViews(copy, mNotificationPeekRow);
+
+                            entry.icon.setBackgroundColor(0x20FFFFFF);
+
+//                          mNotificationPeekRow.setLayoutTransition(
+//                              peekIndex < mNotificationPeekIndex 
+//                                  ? mNotificationPeekScrubLeft
+//                                  : mNotificationPeekScrubRight);
+
+                            mNotificationPeekRow.removeAllViews();
+                            mNotificationPeekRow.addView(copy.row);
+
+                            mNotificationPeekWindow.setVisibility(View.VISIBLE);
+                            mNotificationPanel.setVisibility(View.GONE);
+
+                            mNotificationPeekIndex = peekIndex;
+                        }
+                    }
+                    break;
+                case MSG_CLOSE_NOTIFICATION_PEEK:
+                    if (DEBUG) Slog.d(TAG, "closing notification peek window");
+                    mNotificationPeekWindow.setVisibility(View.GONE);
+                    mNotificationPeekRow.removeAllViews();
+                    final int N = mNotns.size();
+                    if (mNotificationPeekIndex < N) {
+                        NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
+                        entry.icon.setBackgroundColor(0);
+                    }
+                    break;
                 case MSG_OPEN_NOTIFICATION_PANEL:
                     if (DEBUG) Slog.d(TAG, "opening notifications panel");
                     if (mNotificationPanel.getVisibility() == View.GONE) {
+                        mNotificationPeekWindow.setVisibility(View.GONE);
+
                         mDoNotDisturbButton.setText(mNotificationsOn
                                 ? R.string.status_bar_do_not_disturb_button
                                 : R.string.status_bar_please_disturb_button);
@@ -336,7 +441,7 @@
         }
         mNotificationTrigger.setImageResource(resId);
     }
-    
+
     public void setBatteryMeter(int level, boolean plugged) {
         if (DEBUG) Slog.d(TAG, "battery=" + level + (plugged ? " - plugged" : " - unplugged"));
         mBatteryMeter.setImageResource(R.drawable.sysbar_batterymini);
@@ -590,6 +695,14 @@
                 visible ? R.anim.navigation_in : R.anim.navigation_out);
     }
 
+    public void setIMEButtonVisible(boolean visible) {
+
+        if (DEBUG) {
+            Slog.d(TAG, (visible?"showing":"hiding") + " the IME button");
+        }
+        mInputMethodButton.setIMEButtonVisible(visible);
+    }
+
     private void setAreThereNotifications() {
         final boolean hasClearable = mNotns.hasClearableItems();
 
@@ -812,6 +925,72 @@
         return entry.notification;
     }
 
+    private class NotificationIconTouchListener implements View.OnTouchListener {
+        VelocityTracker mVT;
+
+        public NotificationIconTouchListener() {
+        }
+
+        public boolean onTouch(View v, MotionEvent event) {
+            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
+            boolean panelShowing = mNotificationPanel.getVisibility() != View.GONE;
+            if (panelShowing) return false;
+
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    mVT = VelocityTracker.obtain();
+
+                    // fall through
+                case MotionEvent.ACTION_OUTSIDE:
+                case MotionEvent.ACTION_MOVE:
+                    // peek and switch icons if necessary
+                    int numIcons = mIconLayout.getChildCount();
+                    int peekIndex = 
+                            (int)((float)event.getX() * numIcons / mIconLayout.getWidth());
+                    if (peekIndex > numIcons - 1) peekIndex = numIcons - 1;
+                    else if (peekIndex < 0) peekIndex = 0;
+
+                    if (!peeking || mNotificationPeekIndex != peekIndex) {
+                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex);
+                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
+                        peekMsg.arg1 = peekIndex;
+
+                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
+
+                        // no delay if we're scrubbing left-right
+                        mHandler.sendMessageDelayed(peekMsg,
+                                peeking ? 0 : mNotificationPeekTapDuration);
+                    }
+
+                    // check for fling
+                    if (mVT != null) {
+                        mVT.addMovement(event);
+                        mVT.computeCurrentVelocity(1000);
+                        // require a little more oomph once we're already in peekaboo mode
+                        if (!panelShowing && (
+                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
+                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
+                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
+                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
+                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
+                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
+                        }
+                    }
+                    return true;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
+                    if (peeking) {
+                        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 250);
+                    }
+                    mVT.recycle();
+                    mVT = null;
+                    return true;
+            }
+            return false;
+        }
+    }
+
     StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
         if (DEBUG) {
             Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
@@ -836,6 +1015,7 @@
                     + notification);
             return null;
         }
+
         // Add the icon.
         mNotns.add(entry);
         refreshIcons();
@@ -847,28 +1027,76 @@
         // XXX: need to implement a new limited linear layout class
         // to avoid removing & readding everything
 
+        final int ICON_LIMIT = 4;
+        final LinearLayout.LayoutParams params
+            = new LinearLayout.LayoutParams(mIconSize, mIconSize);
+
         int N = mNotns.size();
-        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize);
 
         if (DEBUG) {
-            Slog.d(TAG, "refreshing icons (" + N + " notifications, mIconLayout="
-                    + mIconLayout + ", mPile=" + mPile);
+            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
         }
 
-        mIconLayout.removeAllViews();
-        for (int i=0; i<4; i++) {
+        ArrayList<View> toShow = new ArrayList<View>();
+
+        for (int i=0; i<ICON_LIMIT; i++) {
             if (i>=N) break;
-            mIconLayout.addView(mNotns.get(N-i-1).icon, i, params);
+            toShow.add(mNotns.get(N-i-1).icon);
         }
 
-        mPile.removeAllViews();
-        for (int i=0; i<N; i++) {
-            mPile.addView(mNotns.get(N-i-1).row);
+        ArrayList<View> toRemove = new ArrayList<View>();
+        for (int i=0; i<mIconLayout.getChildCount(); i++) {
+            View child = mIconLayout.getChildAt(i);
+            if (!toShow.contains(child)) {
+                toRemove.add(child);
+            }
         }
 
+        for (View remove : toRemove) {
+            mIconLayout.removeView(remove);
+        }
+
+        for (int i=0; i<toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mIconLayout.addView(toShow.get(i), i, params);
+            }
+        }
+
+        loadNotificationPanel();
         refreshNotificationTrigger();
     }
 
+    private void loadNotificationPanel() {
+        int N = mNotns.size();
+
+        ArrayList<View> toShow = new ArrayList<View>();
+
+        for (int i=0; i<N; i++) {
+            View row = mNotns.get(N-i-1).row;
+            toShow.add(row);
+        }
+
+        ArrayList<View> toRemove = new ArrayList<View>();
+        for (int i=0; i<mPile.getChildCount(); i++) {
+            View child = mPile.getChildAt(i);
+            if (!toShow.contains(child)) {
+                toRemove.add(child);
+            }
+        }
+
+        for (View remove : toRemove) {
+            mPile.removeView(remove);
+        }
+
+        for (int i=0; i<toShow.size(); i++) {
+            View v = toShow.get(i);
+            if (v.getParent() == null) {
+                mPile.addView(toShow.get(i));
+            }
+        }
+    }
+
     private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
         StatusBarNotification sbn = entry.notification;
         RemoteViews remoteViews = sbn.notification.contentView;
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 3f378e1..07da0fa 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -940,6 +940,23 @@
         }
     }
 
+    public void setIMEButtonVisible(IBinder token, boolean visible) {
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (token == null || mCurToken != token) {
+                Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token);
+                return;
+            }
+
+            synchronized (mMethodMap) {
+                mStatusBar.setIMEButtonVisible(visible);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     void updateFromSettingsLocked() {
         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 400b31f..95d2b65 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -74,6 +74,8 @@
 
     boolean mMenuVisible = false;
 
+    boolean mIMEButtonVisible = false;
+
     private class DisableRecord implements IBinder.DeathRecipient {
         String pkg;
         int what;
@@ -257,6 +259,28 @@
         }
     }
 
+    public void setIMEButtonVisible(final boolean visible) {
+        enforceStatusBar();
+
+        if (SPEW) Slog.d(TAG, (visible?"showing":"hiding") + " IME Button");
+
+        synchronized(mLock) {
+            if (mIMEButtonVisible != visible) {
+                mIMEButtonVisible = visible;
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        if (mBar != null) {
+                            try {
+                                mBar.setIMEButtonVisible(visible);
+                            } catch (RemoteException ex) {
+                            }
+                        }
+                    }
+                });
+            }
+        }
+    }
+
     /**
      * This is used for the automatic version of lights-out mode.  Only call this from
      * the window manager.
@@ -345,6 +369,7 @@
         synchronized (mLock) {
             switches[0] = mLightsOn;
             switches[1] = mMenuVisible;
+            switches[2] = mIMEButtonVisible;
         }
     }
 
diff --git a/tests/StatusBar/res/drawable-mdpi/icon1.png b/tests/StatusBar/res/drawable-mdpi/icon1.png
index abfb6fa..a999834 100644
--- a/tests/StatusBar/res/drawable-mdpi/icon1.png
+++ b/tests/StatusBar/res/drawable-mdpi/icon1.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/icon2.png b/tests/StatusBar/res/drawable-mdpi/icon2.png
index 564b38b..b7763cb 100644
--- a/tests/StatusBar/res/drawable-mdpi/icon2.png
+++ b/tests/StatusBar/res/drawable-mdpi/icon2.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification0.png b/tests/StatusBar/res/drawable-mdpi/notification0.png
new file mode 100644
index 0000000..39794f3
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification0.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification1.png b/tests/StatusBar/res/drawable-mdpi/notification1.png
new file mode 100644
index 0000000..b7bbe2b
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification1.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification2.png b/tests/StatusBar/res/drawable-mdpi/notification2.png
new file mode 100644
index 0000000..b014280
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification2.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification3.png b/tests/StatusBar/res/drawable-mdpi/notification3.png
new file mode 100644
index 0000000..2c618211
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification3.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification4.png b/tests/StatusBar/res/drawable-mdpi/notification4.png
new file mode 100644
index 0000000..1e04793
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification4.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification5.png b/tests/StatusBar/res/drawable-mdpi/notification5.png
new file mode 100644
index 0000000..f05dfe5
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification5.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification6.png b/tests/StatusBar/res/drawable-mdpi/notification6.png
new file mode 100644
index 0000000..b807181
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification6.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification7.png b/tests/StatusBar/res/drawable-mdpi/notification7.png
new file mode 100644
index 0000000..63b2544
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification7.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification8.png b/tests/StatusBar/res/drawable-mdpi/notification8.png
new file mode 100644
index 0000000..13c1499
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification8.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notification9.png b/tests/StatusBar/res/drawable-mdpi/notification9.png
new file mode 100644
index 0000000..587d04c
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notification9.png
Binary files differ
diff --git a/tests/StatusBar/res/drawable-mdpi/notificationx.png b/tests/StatusBar/res/drawable-mdpi/notificationx.png
new file mode 100644
index 0000000..4df9804
--- /dev/null
+++ b/tests/StatusBar/res/drawable-mdpi/notificationx.png
Binary files differ
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 9c267d6..38ee1b8 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -44,6 +44,20 @@
     long mActivityCreateTime = System.currentTimeMillis();
     long mChronometerBase = 0;
 
+    final int[] kNumberedIconResIDs = {
+        R.drawable.notification0,
+        R.drawable.notification1,
+        R.drawable.notification2,
+        R.drawable.notification3,
+        R.drawable.notification4,
+        R.drawable.notification5,
+        R.drawable.notification6,
+        R.drawable.notification7,
+        R.drawable.notification8,
+        R.drawable.notification9
+    };
+    final int kUnnumberedIconResID = R.drawable.notificationx;
+
     @Override
     protected String tag() {
         return TAG;
@@ -708,7 +722,8 @@
         new Test("Ten Notifications") {
             public void run() {
                 for (int i = 0; i < 2; i++) {
-                    Notification n = new Notification(NotificationTestList.this, R.drawable.icon2,
+                    Notification n = new Notification(NotificationTestList.this,
+                            kNumberedIconResIDs[i],
                             null, System.currentTimeMillis(), "Persistent #" + i,
                             "Notify me!!!" + i, null);
                     n.flags |= Notification.FLAG_ONGOING_EVENT;
@@ -716,7 +731,8 @@
                     mNM.notify((i+1)*10, n);
                 }
                 for (int i = 2; i < 10; i++) {
-                    Notification n = new Notification(NotificationTestList.this, R.drawable.icon2,
+                    Notification n = new Notification(NotificationTestList.this,
+                            kNumberedIconResIDs[i],
                             null, System.currentTimeMillis(), "Persistent #" + i,
                             "Notify me!!!" + i, null);
                     n.number = i;
@@ -733,6 +749,13 @@
             }
         },
         
+        new Test("Cancel the other two notifications") {
+            public void run() {
+                mNM.cancel(10);
+                mNM.cancel(100);
+            }
+        },
+        
         new Test("Persistent with numbers 1") {
             public void run() {
                 mNM.notify(1, notificationWithNumbers(1));
@@ -776,8 +799,14 @@
     };
 
     private Notification notificationWithNumbers(int num) {
-        Notification n = new Notification(this, R.drawable.icon2, null, System.currentTimeMillis(),
-                "Persistent #2", "Notify me!!!", null);
+        Notification n = new Notification(this,
+                (num >= 0 && num < kNumberedIconResIDs.length)
+                    ? kNumberedIconResIDs[num]
+                    : kUnnumberedIconResID,
+                null,
+                System.currentTimeMillis(),
+                "Notification", "Number=" + num,
+                null);
         n.number = num;
         return n;
     }
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 0179748..f275e39 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -37,20 +37,19 @@
 import java.util.Map;
 
 /**
- * Class that handles an Internet audio call over SIP. {@link SipManager}
- * facilitates instantiating a {@code SipAudioCall} object for making/receiving
- * calls. See {@link SipManager#makeAudioCall} and
- * {@link SipManager#takeAudioCall}.
+ * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager},
+ * using {@link SipManager#makeAudioCall makeAudioCall()} and  {@link SipManager#takeAudioCall
+ * takeAudioCall()}.
  *
- * <p>Requires permissions to use this class:
+ * <p class="note"><strong>Note:</strong> Using this class require the
  *   {@link android.Manifest.permission#INTERNET} and
- *   {@link android.Manifest.permission#USE_SIP}.
- * <br/>Requires permissions to {@link #startAudio}:
+ *   {@link android.Manifest.permission#USE_SIP} permissions.<br/><br/>In addition, {@link
+ *   #startAudio} requires the
  *   {@link android.Manifest.permission#RECORD_AUDIO},
- *   {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
- *   {@link android.Manifest.permission#WAKE_LOCK}.
- * <br/>Requires permissions to {@link #setSpeakerMode}:
- *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+ *   {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and
+ *   {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode
+ *   setSpeakerMode()} requires the
+ *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
  */
 public class SipAudioCall {
     private static final String TAG = SipAudioCall.class.getSimpleName();
@@ -58,7 +57,10 @@
     private static final boolean DONT_RELEASE_SOCKET = false;
     private static final int SESSION_TIMEOUT = 5; // in seconds
 
-    /** Listener class for all event callbacks. */
+    /** Listener for events relating to a SIP call, such as when a call is being
+     * recieved ("on ringing") or a call is outgoing ("on calling").
+     * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
+     */
     public static class Listener {
         /**
          * Called when the call object is ready to make another call.
@@ -199,7 +201,7 @@
 
     /**
      * Sets the listener to listen to the audio call events. The method calls
-     * {@code setListener(listener, false)}.
+     * {@link #setListener setListener(listener, false)}.
      *
      * @param listener to listen to the audio call events of this object
      * @see #setListener(Listener, boolean)
@@ -537,14 +539,14 @@
     /**
      * Initiates an audio call to the specified profile. The attempt will be
      * timed out if the call is not established within {@code timeout} seconds
-     * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
      * @param peerProfile the SIP profile to make the call to
      * @param sipSession the {@link SipSession} for carrying out the call
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
-     * @see Listener.onError
+     * @see Listener#onError
      * @throws SipException if the SIP service fails to create a session for the
      *        call
      */
@@ -582,12 +584,12 @@
      * Puts a call on hold.  When succeeds, {@link Listener#onCallHeld} is
      * called. The attempt will be timed out if the call is not established
      * within {@code timeout} seconds and
-     * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
-     * @see Listener.onError
+     * @see Listener#onError
      * @throws SipException if the SIP service fails to hold the call
      */
     public void holdCall(int timeout) throws SipException {
@@ -604,12 +606,12 @@
     /**
      * Answers a call. The attempt will be timed out if the call is not
      * established within {@code timeout} seconds and
-     * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
-     * @see Listener.onError
+     * @see Listener#onError
      * @throws SipException if the SIP service fails to answer the call
      */
     public void answerCall(int timeout) throws SipException {
@@ -628,12 +630,12 @@
      * Continues a call that's on hold. When succeeds,
      * {@link Listener#onCallEstablished} is called. The attempt will be timed
      * out if the call is not established within {@code timeout} seconds and
-     * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
      * @param timeout the timeout value in seconds. Default value (defined by
      *        SIP protocol) is used if {@code timeout} is zero or negative.
-     * @see Listener.onError
+     * @see Listener#onError
      * @throws SipException if the SIP service fails to unhold the call
      */
     public void continueCall(int timeout) throws SipException {
@@ -788,8 +790,8 @@
 
     /**
      * Puts the device to speaker mode.
-     * <p>Requires permission:
-     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+     * <p class="note"><strong>Note:</strong> Requires the
+     *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
      */
     public void setSpeakerMode(boolean speakerMode) {
         synchronized (this) {
@@ -799,20 +801,21 @@
     }
 
     /**
-     * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
+     * event 0--9 maps to decimal
      * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
      * flash to 16. Currently, event flash is not supported.
      *
      * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
      *        inputs.
-     * @see http://tools.ietf.org/html/rfc2833
      */
     public void sendDtmf(int code) {
         sendDtmf(code, null);
     }
 
     /**
-     * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
+     * event 0--9 maps to decimal
      * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
      * flash to 16. Currently, event flash is not supported.
      *
@@ -890,10 +893,10 @@
     /**
      * Starts the audio for the established call. This method should be called
      * after {@link Listener#onCallEstablished} is called.
-     * <p>Requires permission:
+     * <p class="note"><strong>Note:</strong> Requires the
      *   {@link android.Manifest.permission#RECORD_AUDIO},
      *   {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
-     *   {@link android.Manifest.permission#WAKE_LOCK}.
+     *   {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
      */
     public void startAudio() {
         try {
diff --git a/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java
index 6aee5f1..509728f 100644
--- a/voip/java/android/net/sip/SipErrorCode.java
+++ b/voip/java/android/net/sip/SipErrorCode.java
@@ -17,11 +17,11 @@
 package android.net.sip;
 
 /**
- * Defines error code returned in
- * {@link SipRegistrationListener#onRegistrationFailed},
- * {@link SipSession.Listener#onError},
- * {@link SipSession.Listener#onCallChangeFailed} and
- * {@link SipSession.Listener#onRegistrationFailed}.
+ * Defines error codes returned during SIP actions. For example, during
+ * {@link SipRegistrationListener#onRegistrationFailed onRegistrationFailed()},
+ * {@link SipSession.Listener#onError onError()},
+ * {@link SipSession.Listener#onCallChangeFailed onCallChangeFailed()} and
+ * {@link SipSession.Listener#onRegistrationFailed onRegistrationFailed()}.
  */
 public class SipErrorCode {
     /** Not an error. */
diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java
index 225b94f..0339395 100644
--- a/voip/java/android/net/sip/SipException.java
+++ b/voip/java/android/net/sip/SipException.java
@@ -17,7 +17,7 @@
 package android.net.sip;
 
 /**
- * General SIP-related exception class.
+ * Indicates a general SIP-related exception.
  */
 public class SipException extends Exception {
     public SipException() {
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 38d2b0c..8aaa805 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -29,30 +29,29 @@
 import java.text.ParseException;
 
 /**
- * The class provides API for various SIP related tasks. Specifically, the API
- * allows an application to:
+ * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related
+ * SIP services. This class is the starting point for any SIP actions. You can acquire an instance
+ * of it with {@link #newInstance newInstance()}.</p>
+ * <p>The APIs in this class allows you to:</p>
  * <ul>
- * <li>open a {@link SipProfile} to get ready for making outbound calls or have
- *      the background SIP service listen to incoming calls and broadcast them
- *      with registered command string. See
- *      {@link #open(SipProfile, PendingIntent, SipRegistrationListener)},
- *      {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
- *      {@link #isRegistered}. It also facilitates handling of the incoming call
- *      broadcast intent. See
- *      {@link #isIncomingCallIntent}, {@link #getCallId},
- *      {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
- * <li>make/take SIP-based audio calls. See
- *      {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
- * <li>register/unregister with a SIP service provider manually. See
- *      {@link #register} and {@link #unregister}.</li>
- * <li>process SIP events directly with a {@link SipSession} created by
- *      {@link #createSipSession}.</li>
+ * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See
+ * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li>
+ * <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may
+ * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls
+ * should be handled with a {@link SipAudioCall}, which you can acquire with {@link
+ * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li>
+ * <li>Register and unregister with a SIP service provider, with
+ *      {@link #register register()} and {@link #unregister unregister()}.</li>
+ * <li>Verify session connectivity, with {@link #isOpened isOpened()} and
+ *      {@link #isRegistered isRegistered()}.</li>
  * </ul>
- * {@code SipManager} can only be instantiated if SIP API is supported by the
- * device. (See {@link #isApiSupported}).
- * <p>Requires permissions to use this class:
- *   {@link android.Manifest.permission#INTERNET} and
- *   {@link android.Manifest.permission#USE_SIP}.
+ * <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using
+ * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported
+ * isVoipSupported()} to verify that the device supports VOIP calling and {@link
+ * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports
+ * the SIP APIs.<br/><br/>Your application must also request the {@link
+ * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
+ * permissions.</p>
  */
 public class SipManager {
     /**
@@ -160,7 +159,7 @@
     }
 
     /**
-     * Opens the profile for making calls. The caller may make subsequent calls
+     * Opens the profile for making generic SIP calls. The caller may make subsequent calls
      * through {@link #makeAudioCall}. If one also wants to receive calls on the
      * profile, use
      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
@@ -179,7 +178,7 @@
     }
 
     /**
-     * Opens the profile for making calls and/or receiving calls. The caller may
+     * Opens the profile for making calls and/or receiving generic SIP calls. The caller may
      * make subsequent calls through {@link #makeAudioCall}. If the
      * auto-registration option is enabled in the profile, the SIP service
      * will register the profile to the corresponding SIP provider periodically
@@ -296,7 +295,7 @@
     /**
      * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
      * out if the call is not established within {@code timeout} seconds and
-     * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
      * @param localProfile the SIP profile to make the call from
@@ -307,7 +306,7 @@
      *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
-     * @see SipAudioCall.Listener.onError
+     * @see SipAudioCall.Listener#onError
      */
     public SipAudioCall makeAudioCall(SipProfile localProfile,
             SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
@@ -327,7 +326,7 @@
      * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
      * timed out if the call is not established within {@code timeout} seconds
      * and
-     * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+     * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
      * @param localProfileUri URI of the SIP profile to make the call from
@@ -338,7 +337,7 @@
      *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
-     * @see SipAudioCall.Listener.onError
+     * @see SipAudioCall.Listener#onError
      */
     public SipAudioCall makeAudioCall(String localProfileUri,
             String peerProfileUri, SipAudioCall.Listener listener, int timeout)
@@ -449,7 +448,7 @@
      * receiving calls.
      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
      * still needed to be called at least once in order for the SIP service to
-     * notify the caller with the {@code PendingIntent} when an incoming call is
+     * notify the caller with the {@link android.app.PendingIntent} when an incoming call is
      * received.
      *
      * @param localProfile the SIP profile to register with
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index dddb07d..6977e30 100644
--- a/voip/java/android/net/sip/SipProfile.java
+++ b/voip/java/android/net/sip/SipProfile.java
@@ -32,7 +32,10 @@
 import javax.sip.address.URI;
 
 /**
- * Class containing a SIP account, domain and server information.
+ * Defines a SIP profile, including a SIP account, domain and server information.
+ * <p>You can create a {@link SipProfile} using {@link
+ * SipProfile.Builder}. You can also retrieve one from a {@link SipSession}, using {@link
+ * SipSession#getLocalProfile} and {@link SipSession#getPeerProfile}.</p>
  */
 public class SipProfile implements Parcelable, Serializable, Cloneable {
     private static final long serialVersionUID = 1L;
@@ -59,7 +62,7 @@
             };
 
     /**
-     * Class to help create a {@code SipProfile}.
+     * Helper class for creating a {@link SipProfile}.
      */
     public static class Builder {
         private AddressFactory mAddressFactory;
diff --git a/voip/java/android/net/sip/SipRegistrationListener.java b/voip/java/android/net/sip/SipRegistrationListener.java
index e1f35ad..9968cc7 100644
--- a/voip/java/android/net/sip/SipRegistrationListener.java
+++ b/voip/java/android/net/sip/SipRegistrationListener.java
@@ -17,7 +17,7 @@
 package android.net.sip;
 
 /**
- * Listener class to listen to SIP registration events.
+ * Listener for SIP registration events.
  */
 public interface SipRegistrationListener {
     /**
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
index 9c08e46..5629b3c 100644
--- a/voip/java/android/net/sip/SipSession.java
+++ b/voip/java/android/net/sip/SipSession.java
@@ -20,14 +20,17 @@
 import android.util.Log;
 
 /**
- * A SIP session that is associated with a SIP dialog or a standalone
+ * Represents a SIP session that is associated with a SIP dialog or a standalone
  * transaction not within a dialog.
+ * <p>You can get a {@link SipSession} from {@link SipManager} with {@link
+ * SipManager#createSipSession createSipSession()} (when initiating calls) or {@link
+ * SipManager#getSessionFor getSessionFor()} (when receiving calls).</p>
  */
 public final class SipSession {
     private static final String TAG = "SipSession";
 
     /**
-     * Defines {@link SipSession} states.
+     * Defines SIP session states, such as "registering", "outgoing call", and "in call".
      */
     public static class State {
         /** When session is ready to initiate a call or transaction. */
@@ -98,7 +101,9 @@
     }
 
     /**
-     * Listener class that listens to {@link SipSession} events.
+     * Listener for events relating to a SIP session, such as when a session is being registered
+     * ("on registering") or a call is outgoing ("on calling").
+     * <p>Many of these events are also received by {@link SipAudioCall.Listener}.</p>
      */
     public static class Listener {
         /**
diff --git a/voip/java/android/net/sip/package.html b/voip/java/android/net/sip/package.html
new file mode 100644
index 0000000..790656b
--- /dev/null
+++ b/voip/java/android/net/sip/package.html
@@ -0,0 +1,39 @@
+<HTML>
+<BODY>
+<p>Provides access to Session Initiation Protocol (SIP) functionality, such as
+making and answering VOIP calls using SIP.</p>
+
+<p>To get started, you need to get an instance of the {@link android.net.sip.SipManager} by
+calling {@link android.net.sip.SipManager#newInstance newInstance()}.</p>
+
+<p>With the {@link android.net.sip.SipManager}, you can initiate SIP audio calls with {@link
+android.net.sip.SipManager#makeAudioCall makeAudioCall()} and {@link
+android.net.sip.SipManager#takeAudioCall takeAudioCall()}. Both methods require
+a {@link android.net.sip.SipAudioCall.Listener} that receives callbacks when the state of the
+call changes, such as when the call is ringing, established, or ended.</p>
+
+<p>Both {@link android.net.sip.SipManager#makeAudioCall makeAudioCall()} also requires two
+{@link android.net.sip.SipProfile} objects, representing the local device and the peer
+device. You can create a {@link android.net.sip.SipProfile} using the {@link
+android.net.sip.SipProfile.Builder} subclass.</p>
+
+<p>Once you have a {@link android.net.sip.SipAudioCall}, you can perform SIP audio call actions with
+the instance, such as make a call, answer a call, mute a call, turn on speaker mode, send DTMF
+tones, and more.</p>
+
+<p>If you want to create generic SIP connections (such as for video calls or other), you can
+create a SIP connection from the {@link android.net.sip.SipManager}, using {@link
+android.net.sip.SipManager#open open()}. If you only want to create audio SIP calls, though, you
+should use the {@link android.net.sip.SipAudioCall} class, as described above.</p>
+
+<p class="note"><strong>Note:</strong>
+Not all Android-powered devices support VOIP functionality with SIP. Before performing any SIP
+activity, you should call {@link android.net.sip.SipManager#isVoipSupported isVoipSupported()}
+to verify that the device supports VOIP calling and {@link
+android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports the
+SIP APIs.<br/><br/>
+Your application must also request the {@link android.Manifest.permission#INTERNET} and {@link
+android.Manifest.permission#USE_SIP} permissions in order to use the SIP APIs.
+</p>
+</BODY>
+</HTML>
\ No newline at end of file