Merge "Use MCC value to always override channel count setting" into eclair
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 50bfefc..f991df7 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -147,12 +147,9 @@
 
     public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
         ViewConfiguration config = ViewConfiguration.get(context);
-        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
         mContext = context;
         mListener = listener;
         mEdgeSlop = config.getScaledEdgeSlop();
-        mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
-        mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
     }
 
     public boolean onTouchEvent(MotionEvent event) {
@@ -165,6 +162,11 @@
                     event.getPointerCount() >= 2) {
                 // We have a new multi-finger gesture
 
+                // as orientation can change, query the metrics in touch down
+                DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+                mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+                mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
                 // Be paranoid in case we missed an event
                 reset();
 
@@ -185,12 +187,16 @@
                 final float x1 = getRawX(event, 1);
                 final float y1 = getRawY(event, 1);
 
-                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
-                        x1 < edgeSlop || y1 < edgeSlop;
-                boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
-                        x1 > rightSlop || y1 > bottomSlop;
+                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+                        || x0 > rightSlop || y0 > bottomSlop;
+                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+                        || x1 > rightSlop || y1 > bottomSlop;
 
-                if (p0sloppy) {
+                if(p0sloppy && p1sloppy) {
+                    mFocusX = -1;
+                    mFocusY = -1;
+                    mSloppyGesture = true;
+                } else if (p0sloppy) {
                     mFocusX = event.getX(1);
                     mFocusY = event.getY(1);
                     mSloppyGesture = true;
@@ -211,12 +217,15 @@
                 final float x1 = getRawX(event, 1);
                 final float y1 = getRawY(event, 1);
 
-                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
-                x1 < edgeSlop || y1 < edgeSlop;
-                boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
-                x1 > rightSlop || y1 > bottomSlop;
+                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+                        || x0 > rightSlop || y0 > bottomSlop;
+                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+                        || x1 > rightSlop || y1 > bottomSlop;
 
-                if (p0sloppy) {
+                if(p0sloppy && p1sloppy) {
+                    mFocusX = -1;
+                    mFocusY = -1;
+                } else if (p0sloppy) {
                     mFocusX = event.getX(1);
                     mFocusY = event.getY(1);
                 } else if (p1sloppy) {
@@ -226,6 +235,14 @@
                     mSloppyGesture = false;
                     mGestureInProgress = mListener.onScaleBegin(this);
                 }
+            } else if ((action == MotionEvent.ACTION_POINTER_1_UP
+                    || action == MotionEvent.ACTION_POINTER_2_UP)
+                    && mSloppyGesture) {
+                // Set focus point to the remaining finger
+                int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+                        >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+                mFocusX = event.getX(id);
+                mFocusY = event.getY(id);
             }
         } else {
             // Transform gesture in progress - attempt to handle it
diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index 8e25395..dca52f6 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -32,6 +32,8 @@
     public static final boolean CALLBACK_PROXY = false;
     public static final boolean COOKIE_MANAGER = false;
     public static final boolean COOKIE_SYNC_MANAGER = false;
+    public static final boolean DRAG_TRACKER = false;
+    public static final String DRAG_TRACKER_LOGTAG = "skia";
     public static final boolean FRAME_LOADER = false;
     public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE
     public static final boolean LOAD_LISTENER = false;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5b4ec60..46064a2 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -200,6 +200,8 @@
         implements ViewTreeObserver.OnGlobalFocusChangeListener,
         ViewGroup.OnHierarchyChangeListener {
 
+    // enable debug output for drag trackers
+    private static final boolean DEBUG_DRAG_TRACKER = false;
     // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
     // the screen all-the-time. Good for profiling our drawing code
     static private final boolean AUTO_REDRAW_HACK = false;
@@ -534,8 +536,10 @@
     static int DEFAULT_SCALE_PERCENT;
     private float mDefaultScale;
 
-    // set to true temporarily while the zoom control is being dragged
+    // set to true temporarily during ScaleGesture triggered zoom
     private boolean mPreviewZoomOnly = false;
+    // extra scale during zoom preview
+    private float mPreviewExtraZoomScale = 1.0f;
 
     // computed scale and inverse, from mZoomWidth.
     private float mActualScale;
@@ -2796,16 +2800,7 @@
         return super.drawChild(canvas, child, drawingTime);
     }
 
-    @Override
-    protected void onDraw(Canvas canvas) {
-        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
-        if (mNativeClass == 0) {
-            return;
-        }
-        int saveCount = canvas.save();
-        if (mTitleBar != null) {
-            canvas.translate(0, (int) mTitleBar.getHeight());
-        }
+    private void drawContent(Canvas canvas) {
         // Update the buttons in the picture, so when we draw the picture
         // to the screen, they are in the correct state.
         // Tell the native side if user is a) touching the screen,
@@ -2817,11 +2812,34 @@
         nativeRecordButtons(hasFocus() && hasWindowFocus(),
                 mTouchMode == TOUCH_SHORTPRESS_START_MODE
                 || mTrackballDown || mGotCenterDown, false);
-        drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
+        // use the DKGRAY as background when drawing zoom preview
+        drawCoreAndCursorRing(canvas, mPreviewZoomOnly ? Color.DKGRAY
+                : mBackgroundColor, mDrawCursorRing);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
+        if (mNativeClass == 0) {
+            return;
+        }
+        int saveCount = canvas.save();
+        if (mPreviewZoomOnly) {
+            // scale after canvas.save() so that the child, like titlebar, will
+            // not be scaled.
+            canvas.scale(mPreviewExtraZoomScale, mPreviewExtraZoomScale,
+                    mZoomCenterX + mScrollX, mZoomCenterY + mScrollY);
+        }
+        if (mTitleBar != null) {
+            canvas.translate(0, (int) mTitleBar.getHeight());
+        }
+        if (mDragTrackerHandler == null || !mDragTrackerHandler.draw(canvas)) {
+            drawContent(canvas);
+        }
         canvas.restoreToCount(saveCount);
 
-        // Now draw the shadow.
-        if (mTitleBar != null) {
+        // Now draw the shadow, skip if it is in zoom preview mode.
+        if ((mTitleBar != null && !mPreviewZoomOnly)) {
             int y = mScrollY + getVisibleTitleHeight();
             int height = (int) (5f * getContext().getResources()
                     .getDisplayMetrics().density);
@@ -3726,6 +3744,8 @@
     private class ScaleDetectorListener implements
             ScaleGestureDetector.OnScaleGestureListener {
 
+        float mStartX, mStartY;
+
         public boolean onScaleBegin(ScaleGestureDetector detector) {
             // cancel the single touch handling
             cancelTouch();
@@ -3739,6 +3759,9 @@
             if (inEditingMode() && nativeFocusCandidateIsPassword()) {
                 mWebTextView.setInPassword(false);
             }
+            mPreviewExtraZoomScale = 1.0f;
+            mStartX = detector.getFocusX();
+            mStartY = detector.getFocusY();
             return true;
         }
 
@@ -3747,13 +3770,12 @@
                 mPreviewZoomOnly = false;
                 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
                 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+                float scale = mPreviewExtraZoomScale * mActualScale;
                 // don't reflow when zoom in; when zoom out, do reflow if the
                 // new scale is almost minimum scale;
-                boolean reflowNow = (mActualScale - mMinZoomScale <= 0.01f)
-                        || ((mActualScale <= 0.8 * mTextWrapScale));
-                // force zoom after mPreviewZoomOnly is set to false so that the
-                // new view size will be passed to the WebKit
-                setNewZoomScale(mActualScale, reflowNow, true);
+                boolean reflowNow = (scale - mMinZoomScale <= 0.01f)
+                        || ((scale <= 0.8 * mTextWrapScale));
+                setNewZoomScale(scale, reflowNow, false);
                 // call invalidate() to draw without zoom filter
                 invalidate();
             }
@@ -3770,19 +3792,34 @@
         }
 
         public boolean onScale(ScaleGestureDetector detector) {
+            float currScale = mPreviewExtraZoomScale * mActualScale;
             float scale = (float) (Math.round(detector.getScaleFactor()
-                    * mActualScale * 100) / 100.0);
-            if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
+                    * currScale * 100) / 100.0);
+            // limit the scale change per step
+            if (scale > currScale) {
+                scale = Math.min(scale, currScale * 1.25f);
+            } else {
+                // the preview scale can be 80% of mMinZoomScale for feedback
+                scale = Math.max(Math.max(scale, currScale * 0.8f),
+                        mMinZoomScale * 0.8f);
+            }
+            if (Math.abs(scale - currScale) >= PREVIEW_SCALE_INCREMENT) {
                 mPreviewZoomOnly = true;
-                // limit the scale change per step
-                if (scale > mActualScale) {
-                    scale = Math.min(scale, mActualScale * 1.25f);
+                // FIXME: mZoomCenterX/Y need to be relative to mActualScale.
+                // Ideally the focusX/Y should be a fixed point. But currently
+                // it just returns the center of the two pointers. If only one
+                // pointer is moving, the center is shifting. Currently we only
+                // adjust it for zoom in case to get better result.
+                if (mPreviewExtraZoomScale > 1.0f) {
+                    mZoomCenterX = mStartX - (mStartX - detector.getFocusX())
+                            / mPreviewExtraZoomScale;
+                    mZoomCenterY = mStartY - (mStartY - detector.getFocusY())
+                            / mPreviewExtraZoomScale;
                 } else {
-                    scale = Math.max(scale, mActualScale * 0.8f);
+                    mZoomCenterX = detector.getFocusX();
+                    mZoomCenterY = detector.getFocusY();
                 }
-                mZoomCenterX = detector.getFocusX();
-                mZoomCenterY = detector.getFocusY();
-                setNewZoomScale(scale, false, false);
+                mPreviewExtraZoomScale = scale / mActualScale;
                 invalidate();
                 return true;
             }
@@ -3790,6 +3827,162 @@
         }
     }
 
+    // if the page can scroll <= this value, we won't allow the drag tracker
+    // to have any effect.
+    private static final int MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER = 4;
+
+    private class DragTrackerHandler {
+        private final DragTracker mProxy;
+        private final float mStartY, mStartX;
+        private final float mMinDY, mMinDX;
+        private final float mMaxDY, mMaxDX;
+        private float mCurrStretchY, mCurrStretchX;
+        private int mSX, mSY;
+
+        public DragTrackerHandler(float x, float y, DragTracker proxy) {
+            mProxy = proxy;
+
+            int docBottom = computeVerticalScrollRange() + getTitleHeight();
+            int viewTop = getScrollY();
+            int viewBottom = viewTop + getHeight();
+
+            mStartY = y;
+            mMinDY = -viewTop;
+            mMaxDY = docBottom - viewBottom;
+
+            if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+                Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " dragtracker y= " + y +
+                      " up/down= " + mMinDY + " " + mMaxDY);
+            }
+
+            int docRight = computeHorizontalScrollRange();
+            int viewLeft = getScrollX();
+            int viewRight = viewLeft + getWidth();
+            mStartX = x;
+            mMinDX = -viewLeft;
+            mMaxDX = docRight - viewRight;
+
+            mProxy.onStartDrag(x, y);
+
+            // ensure we buildBitmap at least once
+            mSX = -99999;
+        }
+
+        private float computeStretch(float delta, float min, float max) {
+            float stretch = 0;
+            if (max - min > MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER) {
+                if (delta < min) {
+                    stretch = delta - min;
+                } else if (delta > max) {
+                    stretch = delta - max;
+                }
+            }
+            return stretch;
+        }
+
+        public void dragTo(float x, float y) {
+            float sy = computeStretch(mStartY - y, mMinDY, mMaxDY);
+            float sx = computeStretch(mStartX - x, mMinDX, mMaxDX);
+
+            if (mCurrStretchX != sx || mCurrStretchY != sy) {
+                mCurrStretchX = sx;
+                mCurrStretchY = sy;
+                if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+                    Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx +
+                          " " + sy);
+                }
+                if (mProxy.onStretchChange(sx, sy)) {
+                    invalidate();
+                }
+            }
+        }
+
+        public void stopDrag() {
+            if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+                Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag");
+            }
+            mProxy.onStopDrag();
+        }
+
+        private int hiddenHeightOfTitleBar() {
+            return getTitleHeight() - getVisibleTitleHeight();
+        }
+
+        // need a way to know if 565 or 8888 is the right config for
+        // capturing the display and giving it to the drag proxy
+        private Bitmap.Config offscreenBitmapConfig() {
+            // hard code 565 for now
+            return Bitmap.Config.RGB_565;
+        }
+
+        /*  If the tracker draws, then this returns true, otherwise it will
+         return false, and draw nothing.
+         */
+        public boolean draw(Canvas canvas) {
+            if (mCurrStretchX != 0 || mCurrStretchY != 0) {
+                int sx = getScrollX();
+                int sy = getScrollY() - hiddenHeightOfTitleBar();
+
+                if (mSX != sx || mSY != sy) {
+                    buildBitmap(sx, sy);
+                    mSX = sx;
+                    mSY = sy;
+                }
+
+                int count = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+                canvas.translate(sx, sy);
+                mProxy.onDraw(canvas);
+                canvas.restoreToCount(count);
+                return true;
+            }
+            if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+                Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " -- draw false " +
+                      mCurrStretchX + " " + mCurrStretchY);
+            }
+            return false;
+        }
+
+        private void buildBitmap(int sx, int sy) {
+            int w = getWidth();
+            int h = getViewHeight();
+            Bitmap bm = Bitmap.createBitmap(w, h, offscreenBitmapConfig());
+            Canvas canvas = new Canvas(bm);
+            canvas.translate(-sx, -sy);
+            drawContent(canvas);
+
+            if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
+                Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "--- buildBitmap " + sx +
+                      " " + sy + " " + w + " " + h);
+            }
+            mProxy.onBitmapChange(bm);
+        }
+    }
+
+    /** @hide */
+    public static class DragTracker {
+        public void onStartDrag(float x, float y) {}
+        public boolean onStretchChange(float sx, float sy) {
+            // return true to have us inval the view
+            return false;
+        }
+        public void onStopDrag() {}
+        public void onBitmapChange(Bitmap bm) {}
+        public void onDraw(Canvas canvas) {}
+    }
+
+    /** @hide */
+    public DragTracker getDragTracker() {
+        return mDragTracker;
+    }
+
+    /** @hide */
+    public void setDragTracker(DragTracker tracker) {
+        mDragTracker = tracker;
+    }
+
+    private DragTracker mDragTracker;
+    private DragTrackerHandler mDragTrackerHandler;
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -3801,19 +3994,40 @@
                     + mTouchMode);
         }
 
+        int action;
+        float x, y;
+        long eventTime = ev.getEventTime();
+
         // FIXME: we may consider to give WebKit an option to handle multi-touch
         // events later.
-        if (mSupportMultiTouch && mMinZoomScale < mMaxZoomScale
-                && ev.getPointerCount() > 1) {
-            mLastTouchTime = ev.getEventTime();
-            return mScaleDetector.onTouchEvent(ev);
+        if (mSupportMultiTouch && ev.getPointerCount() > 1) {
+            mScaleDetector.onTouchEvent(ev);
+            if (mScaleDetector.isInProgress()) {
+                mLastTouchTime = eventTime;
+                return true;
+            }
+            x = mScaleDetector.getFocusX();
+            y = mScaleDetector.getFocusY();
+            action = ev.getAction() & MotionEvent.ACTION_MASK;
+            if (action == MotionEvent.ACTION_POINTER_DOWN) {
+                cancelTouch();
+                action = MotionEvent.ACTION_DOWN;
+            } else if (action == MotionEvent.ACTION_POINTER_UP) {
+                // set mLastTouchX/Y to the remaining point
+                mLastTouchX = x;
+                mLastTouchY = y;
+            } else if (action == MotionEvent.ACTION_MOVE) {
+                // negative x or y indicate it is on the edge, skip it.
+                if (x < 0 || y < 0) {
+                    return true;
+                }
+            }
+        } else {
+            action = ev.getAction();
+            x = ev.getX();
+            y = ev.getY();
         }
 
-        int action = ev.getAction();
-        float x = ev.getX();
-        float y = ev.getY();
-        long eventTime = ev.getEventTime();
-
         // Due to the touch screen edge effect, a touch closer to the edge
         // always snapped to the edge. As getViewWidth() can be different from
         // getWidth() due to the scrollbar, adjusting the point to match
@@ -3888,6 +4102,10 @@
                 }
                 // Remember where the motion event started
                 startTouch(x, y, eventTime);
+                if (mDragTracker != null) {
+                    mDragTrackerHandler = new DragTrackerHandler(x, y,
+                                                                 mDragTracker);
+                }
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -4045,6 +4263,10 @@
                     }
                 }
 
+                if (mDragTrackerHandler != null) {
+                    mDragTrackerHandler.dragTo(x, y);
+                }
+
                 if (done) {
                     // keep the scrollbar on the screen even there is no scroll
                     awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
@@ -4056,6 +4278,10 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
+                if (mDragTrackerHandler != null) {
+                    mDragTrackerHandler.stopDrag();
+                    mDragTrackerHandler = null;
+                }
                 mLastTouchUpTime = eventTime;
                 switch (mTouchMode) {
                     case TOUCH_DOUBLE_TAP_MODE: // double tap
@@ -4131,6 +4357,10 @@
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
+                if (mDragTrackerHandler != null) {
+                    mDragTrackerHandler.stopDrag();
+                    mDragTrackerHandler = null;
+                }
                 cancelTouch();
                 break;
             }
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index dc72008..7f526d1 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -27,6 +27,9 @@
 #include "SkShader.h"
 #include "SkTemplates.h"
 
+#include "SkBoundaryPatch.h"
+#include "SkMeshUtils.h"
+
 #define TIME_DRAWx
 
 static uint32_t get_thread_msec() {
@@ -965,6 +968,42 @@
     {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches}
 };
 
+///////////////////////////////////////////////////////////////////////////////
+
+static void BoundaryPatch_computeCubic(JNIEnv* env, jobject, jfloatArray jpts,
+                                       int texW, int texH, int rows, int cols,
+                                       jfloatArray jverts, jshortArray jidx) {
+    AutoJavaFloatArray ptsArray(env, jpts, 24);
+
+    int vertCount = rows * cols;
+    AutoJavaFloatArray vertsArray(env, jverts, vertCount * 4);
+    SkPoint* verts = (SkPoint*)vertsArray.ptr();
+    SkPoint* texs = verts + vertCount;
+
+    int idxCount = (rows - 1) * (cols - 1) * 6;
+    AutoJavaShortArray idxArray(env, jidx, idxCount);
+    uint16_t* idx = (uint16_t*)idxArray.ptr();  // cast from int16_t*
+
+    SkCubicBoundary cubic;
+    memcpy(cubic.fPts, ptsArray.ptr(), 12 * sizeof(SkPoint));
+
+    SkBoundaryPatch patch;
+    patch.setBoundary(&cubic);
+    // generate our verts
+    patch.evalPatch(verts, rows, cols);
+
+    SkMeshIndices mesh;
+    // generate our texs and idx
+    mesh.init(texs, idx, texW, texH, rows, cols);
+}
+
+static JNINativeMethod gBoundaryPatchMethods[] = {
+    {"nativeComputeCubicPatch", "([FIIII[F[S)V",
+    (void*)BoundaryPatch_computeCubic },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
 #include <android_runtime/AndroidRuntime.h>
 
 #define REG(env, name, array) \
@@ -976,7 +1015,8 @@
     int result;
 
     REG(env, "android/graphics/Canvas", gCanvasMethods);
-    
+    REG(env, "android/graphics/utils/BoundaryPatch", gBoundaryPatchMethods);
+
     return result;
 }
 
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 238ece1..7c7bfeb 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -46,7 +46,7 @@
 }
  
 static void Typeface_unref(JNIEnv* env, jobject obj, SkTypeface* face) {
-    face->unref();
+    SkSafeUnref(face);
 }
 
 static int Typeface_getStyle(JNIEnv* env, jobject obj, SkTypeface* face) {
diff --git a/docs/html/guide/developing/debug-tasks.jd b/docs/html/guide/developing/debug-tasks.jd
index 975f6998..a980efc 100644
--- a/docs/html/guide/developing/debug-tasks.jd
+++ b/docs/html/guide/developing/debug-tasks.jd
@@ -80,13 +80,25 @@
 
 <h2 id="additionaldebugging">Debugging and Testing with Dev Tools</h2>
 
-<p>With the Dev Tools application, you can turn on a number of settings that will
-make it easier to test and debug your applications. The Dev Tools application is automatically
-installed on all system images included with the SDK. The source code for the Dev Tools application
-is also provided in the SDK samples so that you may build it and then install the application on any
-development device.</p>
+<p>With the Dev Tools application, you can enable a number of settings on your device that will
+make it easier to test and debug your applications.</p>
 
-<p>To get to the development settings page on the emulator, launch the Dev Tools application and
+<p>The Dev Tools application is installed by default
+on all system images included with the SDK, so you can use it with the Android Emulator. If you'd
+like to install the Dev Tools application on a real development device, you can copy the
+application from your emulator and then install it on your device using ADB. To copy the
+application from a running emulator, execute:
+</p>
+<pre>
+adb -e pull /system/app/Development.apk ./Development.apk
+</pre>
+<p>This copies the .apk file into the current directory. Then install it on your connected device
+with:</p>
+<pre>
+adb -d install Development.apk
+</pre>
+
+<p>To get started, launch the Dev Tools application and
 select Development Settings. This will open the Development Settings page with the
 following options (among others):</p>
 
@@ -132,7 +144,7 @@
         can happen during debugging.</dd>
 </dl>
 
-<p>These settings will be remembered across emulator restarts. </p>
+<p>These settings will be remembered across emulator restarts.</p>
 
 <h2 id="DebuggingWebPages">Debugging Web Pages</h2>
 
diff --git a/graphics/java/android/graphics/utils/BoundaryPatch.java b/graphics/java/android/graphics/utils/BoundaryPatch.java
new file mode 100644
index 0000000..1cd5e13
--- /dev/null
+++ b/graphics/java/android/graphics/utils/BoundaryPatch.java
@@ -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.
+ */
+
+package android.graphics.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+
+/**
+ * @hide
+ */
+public class BoundaryPatch {
+    private Paint   mPaint;
+    private Bitmap  mTexture;
+    private int     mRows;
+    private int     mCols;
+    private float[] mCubicPoints;
+    private boolean mDirty;
+    // these are the computed output of the native code
+    private float[] mVerts;
+    private short[] mIndices;
+
+    public BoundaryPatch() {
+        mRows = mCols = 2;  // default minimum
+        mCubicPoints = new float[24];
+        mPaint = new Paint();
+        mPaint.setDither(true);
+        mPaint.setFilterBitmap(true);
+        mDirty = true;
+    }
+
+    /**
+     * Set the boundary to be 4 cubics. This takes a single array of floats,
+     * and picks up the 12 pairs starting at offset, and treats them as
+     * the x,y coordinates of the cubic control points. The points wrap around
+     * a patch, as follows. For documentation purposes, pts[i] will mean the
+     * x,y pair of floats, as if pts[] were an array of "points".
+     *
+     * Top: pts[0..3]
+     * Right: pts[3..6]
+     * Bottom: pts[6..9]
+     * Right: pts[9..11], pts[0]
+     *
+     * The coordinates are copied from the input array, so subsequent changes
+     * to pts[] will not be reflected in the boundary.
+     *
+     * @param pts The src array of x,y pairs for the boundary cubics
+     * @param offset The index into pts of the first pair
+     * @param rows The number of points across to approximate the boundary.
+     *             Must be >= 2, though very large values may slow down drawing
+     * @param cols The number of points down to approximate the boundary.
+     *             Must be >= 2, though very large values may slow down drawing
+     */
+    public void setCubicBoundary(float[] pts, int offset, int rows, int cols) {
+        if (rows < 2 || cols < 2) {
+            throw new RuntimeException("rows and cols must be >= 2");
+        }
+        System.arraycopy(pts, offset, mCubicPoints, 0, 24);
+        if (mRows != rows || mCols != cols) {
+            mRows = rows;
+            mCols = cols;
+        }
+        mDirty = true;
+    }
+
+    /**
+     * Reference a bitmap texture to be mapped onto the patch.
+     */
+    public void setTexture(Bitmap texture) {
+        if (mTexture != texture) {
+            if (mTexture == null ||
+                    mTexture.getWidth() != texture.getWidth() ||
+                    mTexture.getHeight() != texture.getHeight()) {
+                // need to recompute texture coordinates
+                mDirty = true;
+            }
+            mTexture = texture;
+            mPaint.setShader(new BitmapShader(texture,
+                                              Shader.TileMode.CLAMP,
+                                              Shader.TileMode.CLAMP));
+        }
+    }
+
+    /**
+     * Return the paint flags for the patch
+     */
+    public int getPaintFlags() {
+        return mPaint.getFlags();
+    }
+
+    /**
+     * Set the paint flags for the patch
+     */
+    public void setPaintFlags(int flags) {
+        mPaint.setFlags(flags);
+    }
+
+    /**
+     * Set the xfermode for the patch
+     */
+    public void setXfermode(Xfermode mode) {
+        mPaint.setXfermode(mode);
+    }
+
+    /**
+     * Set the alpha for the patch
+     */
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    /**
+     * Draw the patch onto the canvas.
+     *
+     * setCubicBoundary() and setTexture() must be called before drawing.
+     */
+    public void draw(Canvas canvas) {
+        if (mDirty) {
+            buildCache();
+            mDirty = false;
+        }
+
+        // cut the count in half, since mVerts.length is really the length of
+        // the verts[] and tex[] arrays combined
+        // (tex[] are stored after verts[])
+        int vertCount = mVerts.length >> 1;
+        canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertCount,
+                            mVerts, 0, mVerts, vertCount, null, 0,
+                            mIndices, 0, mIndices.length,
+                            mPaint);
+    }
+
+    private void buildCache() {
+        // we need mRows * mCols points, for verts and another set for textures
+        // so *2 for going from points -> floats, and *2 for verts and textures
+        int vertCount = mRows * mCols * 4;
+        if (mVerts == null || mVerts.length != vertCount) {
+            mVerts = new float[vertCount];
+        }
+
+        int indexCount = (mRows - 1) * (mCols - 1) * 6;
+        if (mIndices == null || mIndices.length != indexCount) {
+            mIndices = new short[indexCount];
+        }
+
+        nativeComputeCubicPatch(mCubicPoints,
+                                mTexture.getWidth(), mTexture.getHeight(),
+                                mRows, mCols, mVerts, mIndices);
+    }
+
+    private static native
+    void nativeComputeCubicPatch(float[] cubicPoints,
+                                 int texW, int texH, int rows, int cols,
+                                 float[] verts, short[] indices);
+}
+
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index e39a357..4aac455 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -489,6 +489,7 @@
 {
     int version;
     int fd;
+    int attempt;
     struct pollfd *new_mFDs;
     device_t **new_devices;
     char **new_device_names;
@@ -500,12 +501,17 @@
     LOGV("Opening device: %s", deviceName);
 
     AutoMutex _l(mLock);
-    
-    fd = open(deviceName, O_RDWR);
+
+    for (attempt = 0; attempt < 10; attempt++) {
+        fd = open(deviceName, O_RDWR);
+        if (fd >= 0) break;
+        usleep(100);
+    }
     if(fd < 0) {
         LOGE("could not open %s, %s\n", deviceName, strerror(errno));
         return -1;
     }
+    LOGV("Opened device: %s (%d failures)", deviceName, attempt);
 
     if(ioctl(fd, EVIOCGVERSION, &version)) {
         LOGE("could not get driver version for %s, %s\n", deviceName, strerror(errno));
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5c0f352..08eceac 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -454,6 +454,7 @@
         IBinder mBinder;
         int mPid;
         int mUid;
+        long mCreateTime;
 
         FeatureUser(int type, String feature, IBinder binder) {
             super();
@@ -462,6 +463,7 @@
             mBinder = binder;
             mPid = getCallingPid();
             mUid = getCallingUid();
+            mCreateTime = System.currentTimeMillis();
 
             try {
                 mBinder.linkToDeath(this, 0);
@@ -476,15 +478,22 @@
 
         public void binderDied() {
             Log.d(TAG, "ConnectivityService FeatureUser binderDied(" +
-                    mNetworkType + ", " + mFeature + ", " + mBinder);
+                    mNetworkType + ", " + mFeature + ", " + mBinder + "), created " +
+                    (System.currentTimeMillis() - mCreateTime) + " mSec ago");
             stopUsingNetworkFeature(this, false);
         }
 
         public void expire() {
             Log.d(TAG, "ConnectivityService FeatureUser expire(" +
-                    mNetworkType + ", " + mFeature + ", " + mBinder);
+                    mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
+                    (System.currentTimeMillis() - mCreateTime) + " mSec ago");
             stopUsingNetworkFeature(this, false);
         }
+
+        public String toString() {
+            return "FeatureUser("+mNetworkType+","+mFeature+","+mPid+","+mUid+"), created " +
+                    (System.currentTimeMillis() - mCreateTime) + " mSec ago";
+        }
     }
 
     // javadoc from interface
@@ -596,6 +605,7 @@
             return stopUsingNetworkFeature(u, true);
         } else {
             // none found!
+            if (DBG) Log.d(TAG, "ignoring stopUsingNetworkFeature - not a live request");
             return 1;
         }
     }
@@ -640,6 +650,7 @@
                     if (x.mUid == u.mUid && x.mPid == u.mPid &&
                             x.mNetworkType == u.mNetworkType &&
                             TextUtils.equals(x.mFeature, u.mFeature)) {
+                        if (DBG) Log.d(TAG, "ignoring stopUsingNetworkFeature as dup is found");
                         return 1;
                     }
                 }
@@ -661,8 +672,8 @@
             tracker =  mNetTrackers[usedNetworkType];
             if(usedNetworkType != networkType) {
                 Integer currentPid = new Integer(pid);
-                reassessPidDns(pid, true);
                 mNetRequestersPids[usedNetworkType].remove(currentPid);
+                reassessPidDns(pid, true);
                 if (mNetRequestersPids[usedNetworkType].size() != 0) {
                     if (DBG) Log.d(TAG, "not tearing down special network - " +
                            "others still using it");
@@ -1198,14 +1209,32 @@
         }
         pw.println();
         for (NetworkStateTracker nst : mNetTrackers) {
-            if (nst.getNetworkInfo().isConnected()) {
-                pw.println("Active network: " + nst.getNetworkInfo().
-                        getTypeName());
+            if (nst != null) {
+                if (nst.getNetworkInfo().isConnected()) {
+                    pw.println("Active network: " + nst.getNetworkInfo().
+                            getTypeName());
+                }
+                pw.println(nst.getNetworkInfo());
+                pw.println(nst);
+                pw.println();
             }
-            pw.println(nst.getNetworkInfo());
-            pw.println(nst);
-            pw.println();
         }
+
+        pw.println("Network Requester Pids:");
+        for (int net : mPriorityList) {
+            String pidString = net + ": ";
+            for (Object pid : mNetRequestersPids[net]) {
+                pidString = pidString + pid.toString() + ", ";
+            }
+            pw.println(pidString);
+        }
+        pw.println();
+
+        pw.println("FeatureUsers:");
+        for (Object requester : mFeatureUsers) {
+            pw.println(requester.toString());
+        }
+        pw.println();
     }
 
     // must be stateless - things change under us.
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index b29e769..3a08e4d 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -329,6 +329,17 @@
             return false;
         }
 
+        /**
+         * Multiple calls to unregisterReceiver() cause exception and a system crash.
+         * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates
+         * disable wifi at the same time.
+         * Avoid doing a disable when the current Wifi state is UNKNOWN
+         * TODO: Handle driver load fail and supplicant lost as seperate states
+         */
+        if (mWifiState == WIFI_STATE_UNKNOWN && !enable) {
+            return false;
+        }
+
         setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid);
 
         if (enable) {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index d014a7e..7b60474 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -748,7 +748,6 @@
     }
 
     private void notifyDefaultData(String reason) {
-        setupDnsProperties();
         setState(State.CONNECTED);
         phone.notifyDataConnection(reason);
         startNetStatPoll();
@@ -757,42 +756,6 @@
         mReregisterOnReconnectFailure = false;
     }
 
-    private void setupDnsProperties() {
-        int mypid = android.os.Process.myPid();
-        String[] servers = getDnsServers(null);
-        String propName;
-        String propVal;
-        int count;
-
-        count = 0;
-        for (int i = 0; i < servers.length; i++) {
-            String serverAddr = servers[i];
-            if (!TextUtils.equals(serverAddr, "0.0.0.0")) {
-                SystemProperties.set("net.dns" + (i+1) + "." + mypid, serverAddr);
-                count++;
-            }
-        }
-        for (int i = count+1; i <= 4; i++) {
-            propName = "net.dns" + i + "." + mypid;
-            propVal = SystemProperties.get(propName);
-            if (propVal.length() != 0) {
-                SystemProperties.set(propName, "");
-            }
-        }
-        /*
-         * Bump the property that tells the name resolver library
-         * to reread the DNS server list from the properties.
-         */
-        propVal = SystemProperties.get("net.dnschange");
-        if (propVal.length() != 0) {
-            try {
-                int n = Integer.parseInt(propVal);
-                SystemProperties.set("net.dnschange", "" + (n+1));
-            } catch (NumberFormatException e) {
-            }
-        }
-    }
-
     /**
      * This is a kludge to deal with the fact that
      * the PDP state change notification doesn't always work