Fix issue #5242779: Device not responding to touch on unlock screen

Rework how we decide when it is okay to turn on the screen by having
the policy call back to the power manager when it knows the lock screen
has been drawn.

Change-Id: Ie8f3f72111dcf7f168723e6dce24e0343b4afe5d
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0dc781f..335c66b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -23,6 +23,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.os.IRemoteCallback;
 import android.view.IApplicationToken;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -220,7 +221,7 @@
     void setPointerSpeed(int speed);
 
     /**
-     * Block until all windows the window manager knows about have been drawn.
+     * Block until the given window has been drawn to the screen.
      */
-    void waitForAllDrawn();
+    void waitForWindowDrawn(IBinder token, in IRemoteCallback callback);
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 980e454..fdbda4c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -772,10 +772,16 @@
      */
     public void screenTurnedOff(int why);
 
+    public interface ScreenOnListener {
+        void onScreenOn();
+    };
+
     /**
-     * Called after the screen turns on.
+     * Called when the power manager would like to turn the screen on.
+     * Must call back on the listener to tell it when the higher-level system
+     * is ready for the screen to go on (i.e. the lock screen is shown).
      */
-    public void screenTurnedOn();
+    public void screenTurningOn(ScreenOnListener screenOnListener);
 
     /**
      * Return whether the screen is currently on.
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
index cbf1c90..91f1527 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Canvas;
+import android.os.IBinder;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.View;
@@ -59,6 +60,10 @@
 
     private boolean mScreenOn = false;
 
+    public interface ShowListener {
+        void onShown(IBinder windowToken);
+    };
+
     /**
      * @param context Used to create views.
      * @param viewManager Keyguard will be attached to this.
@@ -206,7 +211,8 @@
         }
     }
 
-    public synchronized void onScreenTurnedOn() {
+    public synchronized void onScreenTurnedOn(
+            final KeyguardViewManager.ShowListener showListener) {
         if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
         mScreenOn = true;
         if (mKeyguardView != null) {
@@ -214,6 +220,26 @@
 
             // When screen is turned on, need to bind to FaceLock service if we are using FaceLock
             mKeyguardView.bindToFaceLock();
+
+            // Caller should wait for this window to be shown before turning
+            // on the screen.
+            if (mKeyguardHost.getVisibility() == View.VISIBLE) {
+                // Keyguard may be in the process of being shown, but not yet
+                // updated with the window manager...  give it a chance to do so.
+                mKeyguardHost.post(new Runnable() {
+                    @Override public void run() {
+                        if (mKeyguardHost.getVisibility() == View.VISIBLE) {
+                            showListener.onShown(mKeyguardHost.getWindowToken());
+                        } else {
+                            showListener.onShown(null);
+                        }
+                    }
+                });
+            } else {
+                showListener.onShown(null);
+            }
+        } else {
+            showListener.onShown(null);
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index 64a9677..5d3dee9 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -116,7 +116,6 @@
     private static final int KEYGUARD_DONE_AUTHENTICATING = 11;
     private static final int SET_HIDDEN = 12;
     private static final int KEYGUARD_TIMEOUT = 13;
-    private static final int REPORT_SHOW_DONE = 14;
 
     /**
      * The default amount of time we stay awake (used for all key input)
@@ -239,8 +238,6 @@
 
     private boolean mScreenOn = false;
 
-    private boolean mShowPending = false;
-
     // last known state of the cellular connection
     private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
 
@@ -383,19 +380,7 @@
             } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
                 // Do not enable the keyguard if the prox sensor forced the screen off.
             } else {
-                if (!doKeyguardLocked() && why == WindowManagerPolicy.OFF_BECAUSE_OF_USER) {
-                    // The user has explicitly turned off the screen, causing it
-                    // to lock.  We want to block here until the keyguard window
-                    // has shown, so the power manager won't complete the screen
-                    // off flow until that point, so we know it won't turn *on*
-                    // the screen until this is done.
-                    while (mShowPending) {
-                        try {
-                            wait();
-                        } catch (InterruptedException e) {
-                        }
-                    }
-                }
+                doKeyguardLocked();
             }
         }
     }
@@ -403,12 +388,12 @@
     /**
      * Let's us know the screen was turned on.
      */
-    public void onScreenTurnedOn() {
+    public void onScreenTurnedOn(KeyguardViewManager.ShowListener showListener) {
         synchronized (this) {
             mScreenOn = true;
             mDelayedShowingSequence++;
             if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
-            notifyScreenOnLocked();
+            notifyScreenOnLocked(showListener);
         }
     }
 
@@ -573,7 +558,7 @@
      * work that will happen is done; returns false if the caller can wait for
      * the keyguard to be shown.
      */
-    private boolean doKeyguardLocked() {
+    private void doKeyguardLocked() {
         // if another app is disabling us, don't show
         if (!mExternallyEnabled) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
@@ -587,13 +572,13 @@
             // ends (see the broadcast receiver below)
             // TODO: clean this up when we have better support at the window manager level
             // for apps that wish to be on top of the keyguard
-            return true;
+            return;
         }
 
         // if the keyguard is already showing, don't bother
         if (mKeyguardViewManager.isShowing()) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
-            return true;
+            return;
         }
 
         // if the setup wizard hasn't run yet, don't show
@@ -609,18 +594,16 @@
         if (!lockedOrMissing && !provisioned) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
                     + " and the sim is not locked or missing");
-            return true;
+            return;
         }
 
         if (mLockPatternUtils.isLockScreenDisabled()) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
-            return true;
+            return;
         }
 
         if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
-        mShowPending = true;
         showLocked();
-        return false;
     }
 
     /**
@@ -658,9 +641,10 @@
      * @see #onScreenTurnedOn()
      * @see #handleNotifyScreenOn
      */
-    private void notifyScreenOnLocked() {
+    private void notifyScreenOnLocked(KeyguardViewManager.ShowListener showListener) {
         if (DEBUG) Log.d(TAG, "notifyScreenOnLocked");
-        mHandler.sendEmptyMessage(NOTIFY_SCREEN_ON);
+        Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_ON, showListener);
+        mHandler.sendMessage(msg);
     }
 
     /**
@@ -974,7 +958,7 @@
                     handleNotifyScreenOff();
                     return;
                 case NOTIFY_SCREEN_ON:
-                    handleNotifyScreenOn();
+                    handleNotifyScreenOn((KeyguardViewManager.ShowListener)msg.obj);
                     return;
                 case WAKE_WHEN_READY:
                     handleWakeWhenReady(msg.arg1);
@@ -996,12 +980,6 @@
                         doKeyguardLocked();
                     }
                     break;
-                case REPORT_SHOW_DONE:
-                    synchronized (KeyguardViewMediator.this) {
-                        mShowPending = false;
-                        KeyguardViewMediator.this.notifyAll();
-                    }
-                    break;
             }
         }
     };
@@ -1113,12 +1091,6 @@
             playSounds(true);
 
             mShowKeyguardWakeLock.release();
-
-            // We won't say the show is done yet because the view hierarchy
-            // still needs to do the traversal.  Posting this message allows
-            // us to hold off until that is done.
-            Message msg = mHandler.obtainMessage(REPORT_SHOW_DONE);
-            mHandler.sendMessage(msg);
         }
     }
 
@@ -1284,10 +1256,10 @@
      * Handle message sent by {@link #notifyScreenOnLocked()}
      * @see #NOTIFY_SCREEN_ON
      */
-    private void handleNotifyScreenOn() {
+    private void handleNotifyScreenOn(KeyguardViewManager.ShowListener showListener) {
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenOn");
-            mKeyguardViewManager.onScreenTurnedOn();
+            mKeyguardViewManager.onScreenTurnedOn(showListener);
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 10447ad..b229615 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -40,8 +40,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.LocalPowerManager;
 import android.os.Message;
 import android.os.Messenger;
@@ -125,6 +127,7 @@
 import android.view.WindowManagerImpl;
 import android.view.WindowManagerPolicy;
 import android.view.KeyCharacterMap.FallbackAction;
+import android.view.WindowManagerPolicy.ScreenOnListener;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
@@ -2814,23 +2817,30 @@
             updateLockScreenTimeout();
             updateScreenSaverTimeoutLocked();
         }
-        try {
-            mWindowManager.waitForAllDrawn();
-        } catch (RemoteException e) {
-        }
-        // Wait for one frame to give surface flinger time to do its
-        // compositing.  Yes this is a hack, but I am really not up right now for
-        // implementing some mechanism to block until SF is done. :p
-        try {
-            Thread.sleep(20);
-        } catch (InterruptedException e) {
-        }
     }
 
     /** {@inheritDoc} */
-    public void screenTurnedOn() {
+    public void screenTurningOn(final ScreenOnListener screenOnListener) {
         EventLog.writeEvent(70000, 1);
-        mKeyguardMediator.onScreenTurnedOn();
+        //Slog.i(TAG, "Screen turning on...");
+        mKeyguardMediator.onScreenTurnedOn(new KeyguardViewManager.ShowListener() {
+            @Override public void onShown(IBinder windowToken) {
+                if (windowToken != null) {
+                    try {
+                        mWindowManager.waitForWindowDrawn(windowToken, new IRemoteCallback.Stub() {
+                            @Override public void sendResult(Bundle data) {
+                                Slog.i(TAG, "Lock screen displayed!");
+                                screenOnListener.onScreenOn();
+                            }
+                        });
+                    } catch (RemoteException e) {
+                    }
+                } else {
+                    Slog.i(TAG, "No lock screen!");
+                    screenOnListener.onScreenOn();
+                }
+            }
+        });
         synchronized (mLock) {
             mScreenOn = true;
             updateOrientationListenerLp();
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index bb21d81..0934cd0 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -161,7 +161,7 @@
     private int mStayOnConditions = 0;
     private final int[] mBroadcastQueue = new int[] { -1, -1, -1 };
     private final int[] mBroadcastWhy = new int[3];
-    private boolean mBroadcastingScreenOff = false;
+    private boolean mPreparingForScreenOn = false;
     private int mPartialCount = 0;
     private int mPowerState;
     // mScreenOffReason can be WindowManagerPolicy.OFF_BECAUSE_OF_USER,
@@ -1122,7 +1122,8 @@
             pw.println("  mNextTimeout=" + mNextTimeout + " now=" + now
                     + " " + ((mNextTimeout-now)/1000) + "s from now");
             pw.println("  mDimScreen=" + mDimScreen
-                    + " mStayOnConditions=" + mStayOnConditions);
+                    + " mStayOnConditions=" + mStayOnConditions
+                    + " mPreparingForScreenOn=" + mPreparingForScreenOn);
             pw.println("  mScreenOffReason=" + mScreenOffReason
                     + " mUserState=" + mUserState);
             pw.println("  mBroadcastQueue={" + mBroadcastQueue[0] + ',' + mBroadcastQueue[1]
@@ -1341,7 +1342,9 @@
             mBroadcastQueue[0] = on ? 1 : 0;
             mBroadcastQueue[1] = -1;
             mBroadcastQueue[2] = -1;
+            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount);
             mBroadcastWakeLock.release();
+            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount);
             mBroadcastWakeLock.release();
             index = 0;
         }
@@ -1371,6 +1374,21 @@
         }
     }
 
+    private WindowManagerPolicy.ScreenOnListener mScreenOnListener =
+            new WindowManagerPolicy.ScreenOnListener() {
+                @Override public void onScreenOn() {
+                    synchronized (mLocks) {
+                        if (mPreparingForScreenOn) {
+                            mPreparingForScreenOn = false;
+                            updateNativePowerStateLocked();
+                            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP,
+                                    4, mBroadcastWakeLock.mCount);
+                            mBroadcastWakeLock.release();
+                        }
+                    }
+                }
+    };
+
     private Runnable mNotificationTask = new Runnable()
     {
         public void run()
@@ -1387,14 +1405,17 @@
                         mBroadcastWhy[i] = mBroadcastWhy[i+1];
                     }
                     policy = getPolicyLocked();
-                    if (value == 0) {
-                        mBroadcastingScreenOff = true;
+                    if (value == 1 && !mPreparingForScreenOn) {
+                        mPreparingForScreenOn = true;
+                        mBroadcastWakeLock.acquire();
+                        EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND,
+                                mBroadcastWakeLock.mCount);
                     }
                 }
                 if (value == 1) {
                     mScreenOnStart = SystemClock.uptimeMillis();
 
-                    policy.screenTurnedOn();
+                    policy.screenTurningOn(mScreenOnListener);
                     try {
                         ActivityManagerNative.getDefault().wakingUp();
                     } catch (RemoteException e) {
@@ -1432,7 +1453,6 @@
                         synchronized (mLocks) {
                             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3,
                                     mBroadcastWakeLock.mCount);
-                            mBroadcastingScreenOff = false;
                             updateNativePowerStateLocked();
                             mBroadcastWakeLock.release();
                         }
@@ -1464,10 +1484,6 @@
             synchronized (mLocks) {
                 EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0,
                         SystemClock.uptimeMillis() - mScreenOffStart, mBroadcastWakeLock.mCount);
-                synchronized (mLocks) {
-                    mBroadcastingScreenOff = false;
-                    updateNativePowerStateLocked();
-                }
                 mBroadcastWakeLock.release();
             }
         }
@@ -1795,17 +1811,18 @@
     
     private void updateNativePowerStateLocked() {
         if ((mPowerState & SCREEN_ON_BIT) != 0) {
-            // Don't turn screen on if we are currently reporting a screen off.
+            // Don't turn screen on until we know we are really ready to.
             // This is to avoid letting the screen go on before things like the
-            // lock screen have been displayed due to it going off.
-            if (mBroadcastingScreenOff) {
-                // Currently broadcasting that the screen is off.  Don't
-                // allow screen to go on until that is done.
+            // lock screen have been displayed.
+            if (mPreparingForScreenOn) {
+                // Currently waiting for confirmation from the policy that it
+                // is okay to turn on the screen.  Don't allow the screen to go
+                // on until that is done.
                 return;
             }
             for (int i=0; i<mBroadcastQueue.length; i++) {
-                if (mBroadcastQueue[i] == 0) {
-                    // A screen off is currently enqueued.
+                if (mBroadcastQueue[i] == 1) {
+                    // A screen on is currently enqueued.
                     return;
                 }
             }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 15a7ca6..d237953 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -73,6 +73,7 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.LocalPowerManager;
 import android.os.Looper;
 import android.os.Message;
@@ -91,6 +92,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseIntArray;
 import android.util.TypedValue;
@@ -384,6 +386,12 @@
     ArrayList<WindowState> mForceRemoves;
 
     /**
+     * Windows that clients are waiting to have drawn.
+     */
+    ArrayList<Pair<WindowState, IRemoteCallback>> mWaitingForDrawn
+            = new ArrayList<Pair<WindowState, IRemoteCallback>>();
+
+    /**
      * Used when rebuilding window list to keep track of windows that have
      * been removed.
      */
@@ -6295,6 +6303,7 @@
         public static final int DRAG_END_TIMEOUT = 21;
         public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
         public static final int BOOT_TIMEOUT = 23;
+        public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
 
         private Session mLastReportedHold;
 
@@ -6605,11 +6614,6 @@
                     break;
                 }
 
-                case BOOT_TIMEOUT: {
-                    performBootTimeout();
-                    break;
-                }
-
                 case APP_FREEZE_TIMEOUT: {
                     synchronized (mWindowMap) {
                         Slog.w(TAG, "App freeze timeout expired.");
@@ -6678,6 +6682,27 @@
                     notifyHardKeyboardStatusChange();
                     break;
                 }
+
+                case BOOT_TIMEOUT: {
+                    performBootTimeout();
+                    break;
+                }
+
+                case WAITING_FOR_DRAWN_TIMEOUT: {
+                    Pair<WindowState, IRemoteCallback> pair;
+                    synchronized (mWindowMap) {
+                        pair = (Pair<WindowState, IRemoteCallback>)msg.obj;
+                        Slog.w(TAG, "Timeout waiting for drawn: " + pair.first);
+                        if (!mWaitingForDrawn.remove(pair)) {
+                            return;
+                        }
+                    }
+                    try {
+                        pair.second.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                }
             }
         }
     }
@@ -8582,39 +8607,54 @@
             }
         }
 
-        mWindowMap.notifyAll();
+        checkDrawnWindowsLocked();
 
         // Check to see if we are now in a state where the screen should
         // be enabled, because the window obscured flags have changed.
         enableScreenIfNeededLocked();
     }
 
-    public void waitForAllDrawn() {
+    void checkDrawnWindowsLocked() {
+        if (mWaitingForDrawn.size() > 0) {
+            for (int j=mWaitingForDrawn.size()-1; j>=0; j--) {
+                Pair<WindowState, IRemoteCallback> pair = mWaitingForDrawn.get(j);
+                WindowState win = pair.first;
+                //Slog.i(TAG, "Waiting for drawn " + win + ": removed="
+                //        + win.mRemoved + " visible=" + win.isVisibleLw()
+                //        + " shown=" + win.mSurfaceShown);
+                if (win.mRemoved || !win.isVisibleLw()) {
+                    // Window has been removed or made invisible; no draw
+                    // will now happen, so stop waiting.
+                    Slog.w(TAG, "Aborted waiting for drawn: " + pair.first);
+                    try {
+                        pair.second.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    mWaitingForDrawn.remove(pair);
+                    mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
+                } else if (win.mSurfaceShown) {
+                    // Window is now drawn (and shown).
+                    try {
+                        pair.second.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    mWaitingForDrawn.remove(pair);
+                    mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
+                }
+            }
+        }
+    }
+
+    public void waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
         synchronized (mWindowMap) {
-            while (true) {
-                final int N = mWindows.size();
-                boolean okay = true;
-                for (int i=0; i<N && okay; i++) {
-                    WindowState w = mWindows.get(i);
-                    if (DEBUG_SCREEN_ON) {
-                        Slog.i(TAG, "Window " + w + " vis=" + w.isVisibleLw()
-                                + " obscured=" + w.mObscured + " drawn=" + w.isDrawnLw());
-                    }
-                    if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
-                        if (DEBUG_SCREEN_ON) {
-                            Slog.i(TAG, "Window not yet drawn: " + w);
-                        }
-                        okay = false;
-                        break;
-                    }
-                }
-                if (okay) {
-                    return;
-                }
-                try {
-                    mWindowMap.wait();
-                } catch (InterruptedException e) {
-                }
+            WindowState win = windowForClientLocked(null, token, true);
+            if (win != null) {
+                Pair<WindowState, IRemoteCallback> pair =
+                        new Pair<WindowState, IRemoteCallback>(win, callback);
+                Message m = mH.obtainMessage(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
+                mH.sendMessageDelayed(m, 2000);
+                mWaitingForDrawn.add(pair);
+                checkDrawnWindowsLocked();
             }
         }
     }
@@ -9284,6 +9324,15 @@
                 }
             }
         }
+        if (mWaitingForDrawn.size() > 0) {
+            pw.println();
+            pw.println("  Clients waiting for these windows to be drawn:");
+            for (int i=mWaitingForDrawn.size()-1; i>=0; i--) {
+                Pair<WindowState, IRemoteCallback> pair = mWaitingForDrawn.get(i);
+                pw.print("  Waiting #"); pw.print(i); pw.print(' '); pw.print(pair.first);
+                        pw.print(": "); pw.println(pair.second);
+            }
+        }
         pw.println();
         if (mDisplay != null) {
             pw.print("  Display: init="); pw.print(mInitialDisplayWidth); pw.print("x");