Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c3296af authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Improve testibility of applicatin switches.

Things can be flaky, because window focus changes are
dispatched to the window on a separate path from input events,
and the window will drop events if it gets them before it sees
the focus change.  I am trying to mitigate this some by noting
ASAP what the next upcoming focus state will be, so we can check
that and dispatch it before dispatching a key event if needed.

This definitely makes things better, but not perfect.  ctate
suggested that maybe we should be dispatching window focus events
through the input system, which at a glance sounds like a really
really good idea to me...  so maybe we can look at that later.

Also changed the wm command to just be a shell wrapper around
all of the implementation that is now in WindowManagerShellCommand.

And fixed a few places where we write debug info to streams that
would trigger strict mode violations that we really don't care
about.

Test: manual
Change-Id: I5235653bcec5522ab84c7f2e1de96d86f2f59326
parent 0ff7a642
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -2,11 +2,6 @@
#
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := wm
include $(BUILD_JAVA_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := wm
LOCAL_SRC_FILES := wm
+0 −296
Original line number Diff line number Diff line
/*
**
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/


package com.android.commands.wm;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.DisplayMetrics;
import android.system.Os;
import android.view.Display;
import android.view.IWindowManager;
import com.android.internal.os.BaseCommand;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.PrintStream;
import java.lang.Runtime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Wm extends BaseCommand {

    private IWindowManager mWm;

    /**
     * Command-line entry point.
     *
     * @param args The command-line arguments
     */
    public static void main(String[] args) {
        (new Wm()).run(args);
    }

    @Override
    public void onShowUsage(PrintStream out) {
        out.println(
                "usage: wm [subcommand] [options]\n" +
                "       wm size [reset|WxH|WdpxHdp]\n" +
                "       wm density [reset|DENSITY]\n" +
                "       wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]\n" +
                "       wm scaling [off|auto]\n" +
                "       wm screen-capture [userId] [true|false]\n" +
                "\n" +
                "wm size: return or override display size.\n" +
                "         width and height in pixels unless suffixed with 'dp'.\n" +
                "\n" +
                "wm density: override display density.\n" +
                "\n" +
                "wm overscan: set overscan area for display.\n" +
                "\n" +
                "wm scaling: set display scaling mode.\n" +
                "\n" +
                "wm screen-capture: enable/disable screen capture.\n" +
                "\n" +
                "wm dismiss-keyguard: dismiss the keyguard, prompting the user for auth if " +
                "necessary.\n" +
                "\n" +
                "wm surface-trace: log surface commands to stdout in a binary format.\n"
                );
    }

    @Override
    public void onRun() throws Exception {
        mWm = IWindowManager.Stub.asInterface(ServiceManager.checkService(
                        Context.WINDOW_SERVICE));
        if (mWm == null) {
            System.err.println(NO_SYSTEM_ERROR_CODE);
            throw new AndroidException("Can't connect to window manager; is the system running?");
        }

        String op = nextArgRequired();

        if (op.equals("size")) {
            runDisplaySize();
        } else if (op.equals("density")) {
            runDisplayDensity();
        } else if (op.equals("overscan")) {
            runDisplayOverscan();
        } else if (op.equals("scaling")) {
            runDisplayScaling();
        } else if (op.equals("screen-capture")) {
            runSetScreenCapture();
        } else if (op.equals("dismiss-keyguard")) {
            runDismissKeyguard();
        } else if (op.equals("surface-trace")) {
            runSurfaceTrace();
        } else {
            showError("Error: unknown command '" + op + "'");
            return;
        }
    }

    private void runSurfaceTrace() throws Exception {
        ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(FileDescriptor.out);
        mWm.enableSurfaceTrace(pfd);

        try {
            // No one is going to wake us up, we are just waiting on SIGINT. Otherwise
            // the WM can happily continue writing to our stdout.
            synchronized (this) {
                this.wait();
            }
        } finally {
            mWm.disableSurfaceTrace();
        }
    }

    private void runSetScreenCapture() throws Exception {
        String userIdStr = nextArg();
        String enableStr = nextArg();
        int userId;
        boolean disable;

        try {
            userId = Integer.parseInt(userIdStr);
        } catch (NumberFormatException e) {
            System.err.println("Error: bad number " + e);
            return;
        }

        disable = !Boolean.parseBoolean(enableStr);

        try {
            mWm.setScreenCaptureDisabled(userId, disable);
        } catch (RemoteException e) {
            System.err.println("Error: Can't set screen capture " + e);
        }
    }

    private void runDisplaySize() throws Exception {
        String size = nextArg();
        int w, h;
        if (size == null) {
            Point initialSize = new Point();
            Point baseSize = new Point();
            try {
                mWm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, initialSize);
                mWm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, baseSize);
                System.out.println("Physical size: " + initialSize.x + "x" + initialSize.y);
                if (!initialSize.equals(baseSize)) {
                    System.out.println("Override size: " + baseSize.x + "x" + baseSize.y);
                }
            } catch (RemoteException e) {
            }
            return;
        } else if ("reset".equals(size)) {
            w = h = -1;
        } else {
            int div = size.indexOf('x');
            if (div <= 0 || div >= (size.length()-1)) {
                System.err.println("Error: bad size " + size);
                return;
            }
            String wstr = size.substring(0, div);
            String hstr = size.substring(div+1);
            try {
                w = parseDimension(wstr);
                h = parseDimension(hstr);
            } catch (NumberFormatException e) {
                System.err.println("Error: bad number " + e);
                return;
            }
        }

        try {
            if (w >= 0 && h >= 0) {
                // TODO(multidisplay): For now Configuration only applies to main screen.
                mWm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, w, h);
            } else {
                mWm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY);
            }
        } catch (RemoteException e) {
        }
    }

    private void runDisplayDensity() throws Exception {
        String densityStr = nextArg();
        int density;
        if (densityStr == null) {
            try {
                int initialDensity = mWm.getInitialDisplayDensity(Display.DEFAULT_DISPLAY);
                int baseDensity = mWm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY);
                System.out.println("Physical density: " + initialDensity);
                if (initialDensity != baseDensity) {
                    System.out.println("Override density: " + baseDensity);
                }
            } catch (RemoteException e) {
            }
            return;
        } else if ("reset".equals(densityStr)) {
            density = -1;
        } else {
            try {
                density = Integer.parseInt(densityStr);
            } catch (NumberFormatException e) {
                System.err.println("Error: bad number " + e);
                return;
            }
            if (density < 72) {
                System.err.println("Error: density must be >= 72");
                return;
            }
        }

        try {
            if (density > 0) {
                // TODO(multidisplay): For now Configuration only applies to main screen.
                mWm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, density,
                        UserHandle.USER_CURRENT);
            } else {
                mWm.clearForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY,
                        UserHandle.USER_CURRENT);
            }
        } catch (RemoteException e) {
        }
    }

    private void runDisplayOverscan() throws Exception {
        String overscanStr = nextArgRequired();
        Rect rect = new Rect();
        if ("reset".equals(overscanStr)) {
            rect.set(0, 0, 0, 0);
        } else {
            final Pattern FLATTENED_PATTERN = Pattern.compile(
                    "(-?\\d+),(-?\\d+),(-?\\d+),(-?\\d+)");
            Matcher matcher = FLATTENED_PATTERN.matcher(overscanStr);
            if (!matcher.matches()) {
                System.err.println("Error: bad rectangle arg: " + overscanStr);
                return;
            }
            rect.left = Integer.parseInt(matcher.group(1));
            rect.top = Integer.parseInt(matcher.group(2));
            rect.right = Integer.parseInt(matcher.group(3));
            rect.bottom = Integer.parseInt(matcher.group(4));
        }

        try {
            mWm.setOverscan(Display.DEFAULT_DISPLAY, rect.left, rect.top, rect.right, rect.bottom);
        } catch (RemoteException e) {
        }
    }

    private void runDisplayScaling() throws Exception {
        String scalingStr = nextArgRequired();
        if ("auto".equals(scalingStr)) {
            mWm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 0);
        } else if ("off".equals(scalingStr)) {
            mWm.setForcedDisplayScalingMode(Display.DEFAULT_DISPLAY, 1);
        } else {
            System.err.println("Error: scaling must be 'auto' or 'off'");
        }
    }

    private void runDismissKeyguard() throws Exception {
        mWm.dismissKeyguard(null /* callback */);
    }

    private int parseDimension(String s) throws NumberFormatException {
        if (s.endsWith("px")) {
            return Integer.parseInt(s.substring(0, s.length() - 2));
        }
        if (s.endsWith("dp")) {
            int density;
            try {
                density = mWm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY);
            } catch (RemoteException e) {
                density = DisplayMetrics.DENSITY_DEFAULT;
            }
            return Integer.parseInt(s.substring(0, s.length() - 2)) * density /
                    DisplayMetrics.DENSITY_DEFAULT;
        }
        return Integer.parseInt(s);
    }
}
+1 −6
Original line number Diff line number Diff line
#!/system/bin/sh
# Script to start "wm" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/wm.jar
exec app_process $base/bin com.android.commands.wm.Wm "$@"
cmd window "$@"
+100 −77
Original line number Diff line number Diff line
@@ -323,6 +323,13 @@ public final class ViewRootImpl implements ViewParent,
    final Rect mTempRect; // used in the transaction to not thrash the heap.
    final Rect mVisRect; // used to retrieve visible rect of focused view.

    // This is used to reduce the race between window focus changes being dispatched from
    // the window manager and input events coming through the input system.
    @GuardedBy("this")
    boolean mUpcomingWindowFocus;
    @GuardedBy("this")
    boolean mUpcomingInTouchMode;

    public boolean mTraversalScheduled;
    int mTraversalBarrier;
    boolean mWillDrawSoon;
@@ -2452,6 +2459,93 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    private void handleWindowFocusChanged() {
        final boolean hasWindowFocus;
        final boolean inTouchMode;
        synchronized (this) {
            hasWindowFocus = mUpcomingWindowFocus;
            inTouchMode = mUpcomingInTouchMode;
        }

        if (mAttachInfo.mHasWindowFocus == hasWindowFocus) {
            return;
        }

        if (mAdded) {
            profileRendering(hasWindowFocus);

            if (hasWindowFocus) {
                ensureTouchModeLocally(inTouchMode);
                if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
                    mFullRedrawNeeded = true;
                    try {
                        final WindowManager.LayoutParams lp = mWindowAttributes;
                        final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
                        mAttachInfo.mThreadedRenderer.initializeIfNeeded(
                                mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                    } catch (OutOfResourcesException e) {
                        Log.e(mTag, "OutOfResourcesException locking surface", e);
                        try {
                            if (!mWindowSession.outOfMemory(mWindow)) {
                                Slog.w(mTag, "No processes killed for memory;"
                                        + " killing self");
                                Process.killProcess(Process.myPid());
                            }
                        } catch (RemoteException ex) {
                        }
                        // Retry in a bit.
                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
                                MSG_WINDOW_FOCUS_CHANGED), 500);
                        return;
                    }
                }
            }

            mAttachInfo.mHasWindowFocus = hasWindowFocus;

            mLastWasImTarget = WindowManager.LayoutParams
                    .mayUseInputMethod(mWindowAttributes.flags);

            InputMethodManager imm = InputMethodManager.peekInstance();
            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                imm.onPreWindowFocus(mView, hasWindowFocus);
            }
            if (mView != null) {
                mAttachInfo.mKeyDispatchState.reset();
                mView.dispatchWindowFocusChanged(hasWindowFocus);
                mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);

                if (mAttachInfo.mTooltipHost != null) {
                    mAttachInfo.mTooltipHost.hideTooltip();
                }
            }

            // Note: must be done after the focus change callbacks,
            // so all of the view state is set up correctly.
            if (hasWindowFocus) {
                if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                    imm.onPostWindowFocus(mView, mView.findFocus(),
                            mWindowAttributes.softInputMode,
                            !mHasHadWindowFocus, mWindowAttributes.flags);
                }
                // Clear the forward bit.  We can just do this directly, since
                // the window manager doesn't care about it.
                mWindowAttributes.softInputMode &=
                        ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                ((WindowManager.LayoutParams) mView.getLayoutParams())
                        .softInputMode &=
                        ~WindowManager.LayoutParams
                                .SOFT_INPUT_IS_FORWARD_NAVIGATION;
                mHasHadWindowFocus = true;
            } else {
                if (mPointerCapture) {
                    handlePointerCaptureChanged(false);
                }
            }
        }
        mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
    }

    private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
        Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
        try {
@@ -3900,81 +3994,7 @@ public final class ViewRootImpl implements ViewParent,
                    }
                    break;
                case MSG_WINDOW_FOCUS_CHANGED: {
                    final boolean hasWindowFocus = msg.arg1 != 0;
                    if (mAdded) {
                        mAttachInfo.mHasWindowFocus = hasWindowFocus;

                        profileRendering(hasWindowFocus);

                        if (hasWindowFocus) {
                            boolean inTouchMode = msg.arg2 != 0;
                            ensureTouchModeLocally(inTouchMode);
                            if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
                                mFullRedrawNeeded = true;
                                try {
                                    final WindowManager.LayoutParams lp = mWindowAttributes;
                                    final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
                                    mAttachInfo.mThreadedRenderer.initializeIfNeeded(
                                            mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                                } catch (OutOfResourcesException e) {
                                    Log.e(mTag, "OutOfResourcesException locking surface", e);
                                    try {
                                        if (!mWindowSession.outOfMemory(mWindow)) {
                                            Slog.w(mTag, "No processes killed for memory;"
                                                    + " killing self");
                                            Process.killProcess(Process.myPid());
                                        }
                                    } catch (RemoteException ex) {
                                    }
                                    // Retry in a bit.
                                    sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2),
                                            500);
                                    return;
                                }
                            }
                        }

                        mLastWasImTarget = WindowManager.LayoutParams
                                .mayUseInputMethod(mWindowAttributes.flags);

                        InputMethodManager imm = InputMethodManager.peekInstance();
                        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                            imm.onPreWindowFocus(mView, hasWindowFocus);
                        }
                        if (mView != null) {
                            mAttachInfo.mKeyDispatchState.reset();
                            mView.dispatchWindowFocusChanged(hasWindowFocus);
                            mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);

                            if (mAttachInfo.mTooltipHost != null) {
                                mAttachInfo.mTooltipHost.hideTooltip();
                            }
                        }

                        // Note: must be done after the focus change callbacks,
                        // so all of the view state is set up correctly.
                        if (hasWindowFocus) {
                            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                                imm.onPostWindowFocus(mView, mView.findFocus(),
                                        mWindowAttributes.softInputMode,
                                        !mHasHadWindowFocus, mWindowAttributes.flags);
                            }
                            // Clear the forward bit.  We can just do this directly, since
                            // the window manager doesn't care about it.
                            mWindowAttributes.softInputMode &=
                                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                            ((WindowManager.LayoutParams) mView.getLayoutParams())
                                    .softInputMode &=
                                        ~WindowManager.LayoutParams
                                                .SOFT_INPUT_IS_FORWARD_NAVIGATION;
                            mHasHadWindowFocus = true;
                        } else {
                            if (mPointerCapture) {
                                handlePointerCaptureChanged(false);
                            }
                        }
                    }
                    mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
                    handleWindowFocusChanged();
                } break;
                case MSG_DIE:
                    doDie();
@@ -6845,6 +6865,7 @@ public final class ViewRootImpl implements ViewParent,
        }

        if (stage != null) {
            handleWindowFocusChanged();
            stage.deliver(q);
        } else {
            finishInputEvent(q);
@@ -7150,10 +7171,12 @@ public final class ViewRootImpl implements ViewParent,
    }

    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
        synchronized (this) {
            mUpcomingWindowFocus = hasFocus;
            mUpcomingInTouchMode = inTouchMode;
        }
        Message msg = Message.obtain();
        msg.what = MSG_WINDOW_FOCUS_CHANGED;
        msg.arg1 = hasFocus ? 1 : 0;
        msg.arg2 = inTouchMode ? 1 : 0;
        mHandler.sendMessage(msg);
    }

+59 −28
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ShellCommand;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -126,7 +127,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
        if (cmd == null) {
            return handleDefaultCommands(cmd);
        }
        PrintWriter pw = getOutPrintWriter();
        final PrintWriter pw = getOutPrintWriter();
        try {
            switch (cmd) {
                case "start":
@@ -1326,18 +1327,25 @@ final class ActivityManagerShellCommand extends ShellCommand {
        @Override
        public void onUidStateChanged(int uid, int procState, long procStateSeq) throws RemoteException {
            synchronized (this) {
                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                try {
                    mPw.print(uid);
                    mPw.print(" procstate ");
                    mPw.print(ProcessList.makeProcStateString(procState));
                    mPw.print(" seq ");
                    mPw.println(procStateSeq);
                    mPw.flush();
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }

        @Override
        public void onUidGone(int uid, boolean disabled) throws RemoteException {
            synchronized (this) {
                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                try {
                    mPw.print(uid);
                    mPw.print(" gone");
                    if (disabled) {
@@ -1345,21 +1353,31 @@ final class ActivityManagerShellCommand extends ShellCommand {
                    }
                    mPw.println();
                    mPw.flush();
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }

        @Override
        public void onUidActive(int uid) throws RemoteException {
            synchronized (this) {
                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                try {
                    mPw.print(uid);
                    mPw.println(" active");
                    mPw.flush();
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }

        @Override
        public void onUidIdle(int uid, boolean disabled) throws RemoteException {
            synchronized (this) {
                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                try {
                    mPw.print(uid);
                    mPw.print(" idle");
                    if (disabled) {
@@ -1367,24 +1385,37 @@ final class ActivityManagerShellCommand extends ShellCommand {
                    }
                    mPw.println();
                    mPw.flush();
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }

        @Override
        public void onUidCachedChanged(int uid, boolean cached) throws RemoteException {
            synchronized (this) {
                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                try {
                    mPw.print(uid);
                    mPw.println(cached ? " cached" : " uncached");
                    mPw.flush();
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }

        @Override
        public void onOomAdjMessage(String msg) {
            synchronized (this) {
                final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
                try {
                    mPw.print("# ");
                    mPw.println(msg);
                    mPw.flush();
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }

Loading