Bug: 4981385 Simplify the orientation changing code path in the WindowManager. Instead of the policy calling setRotation() when the sensor determined orientation changes, it calls updateRotation(), which figures everything out. For the most part, the rotation actually passed to setRotation() was more or less ignored and just added confusion, particularly when handling deferred orientation changes. Ensure that 180 degree rotations are disallowed even when the application specifies SCREEN_ORIENTATION_SENSOR_*. These rotations are only enabled when docked upside-down for some reason or when the application specifies SCREEN_ORIENTATION_FULL_SENSOR. Ensure that special modes like HDMI connected, lid switch, dock and rotation lock all cause the sensor to be ignored even when the application asks for sensor-based orientation changes. The sensor is not relevant in these modes because some external factor (or the user) is determining the preferred rotation. Currently, applications can still override the preferred rotation even when there are special modes in play that might say otherwise. We could tweak this so that some special modes trump application choices completely (resulting in a letter-boxed application, perhaps). I tested this sort of tweak (not included in the patch) and it seems to work fine, including transitions between applications with varying orientation. Delete dead code related to animFlags. Handle pausing/resuming orientation changes more precisely. Ensure that a deferred orientation change is performed when a drag completes, even if endDragLw() is not called because the drag was aborted before the drop happened. We pause the orientation change in register() and resume in unregister() because those methods appear to always be called as needed. Change-Id: If0a31de3d057251e581fdee64819f2b19e676e9a
418 lines
17 KiB
Java
418 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2011 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.server.wm;
|
|
|
|
import com.android.server.wm.WindowManagerService.H;
|
|
|
|
import android.content.ClipData;
|
|
import android.content.ClipDescription;
|
|
import android.graphics.Region;
|
|
import android.os.IBinder;
|
|
import android.os.Message;
|
|
import android.os.Process;
|
|
import android.os.RemoteException;
|
|
import android.util.Slog;
|
|
import android.view.DragEvent;
|
|
import android.view.InputChannel;
|
|
import android.view.InputQueue;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.view.WindowManager;
|
|
import android.view.WindowManagerPolicy;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
/**
|
|
* Drag/drop state
|
|
*/
|
|
class DragState {
|
|
final WindowManagerService mService;
|
|
IBinder mToken;
|
|
Surface mSurface;
|
|
int mFlags;
|
|
IBinder mLocalWin;
|
|
ClipData mData;
|
|
ClipDescription mDataDescription;
|
|
boolean mDragResult;
|
|
float mCurrentX, mCurrentY;
|
|
float mThumbOffsetX, mThumbOffsetY;
|
|
InputChannel mServerChannel, mClientChannel;
|
|
InputApplicationHandle mDragApplicationHandle;
|
|
InputWindowHandle mDragWindowHandle;
|
|
WindowState mTargetWindow;
|
|
ArrayList<WindowState> mNotifiedWindows;
|
|
boolean mDragInProgress;
|
|
|
|
private final Region mTmpRegion = new Region();
|
|
|
|
DragState(WindowManagerService service, IBinder token, Surface surface,
|
|
int flags, IBinder localWin) {
|
|
mService = service;
|
|
mToken = token;
|
|
mSurface = surface;
|
|
mFlags = flags;
|
|
mLocalWin = localWin;
|
|
mNotifiedWindows = new ArrayList<WindowState>();
|
|
}
|
|
|
|
void reset() {
|
|
if (mSurface != null) {
|
|
mSurface.destroy();
|
|
}
|
|
mSurface = null;
|
|
mFlags = 0;
|
|
mLocalWin = null;
|
|
mToken = null;
|
|
mData = null;
|
|
mThumbOffsetX = mThumbOffsetY = 0;
|
|
mNotifiedWindows = null;
|
|
}
|
|
|
|
void register() {
|
|
if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel");
|
|
if (mClientChannel != null) {
|
|
Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel");
|
|
} else {
|
|
InputChannel[] channels = InputChannel.openInputChannelPair("drag");
|
|
mServerChannel = channels[0];
|
|
mClientChannel = channels[1];
|
|
mService.mInputManager.registerInputChannel(mServerChannel, null);
|
|
InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler,
|
|
mService.mH.getLooper().getQueue());
|
|
|
|
mDragApplicationHandle = new InputApplicationHandle(null);
|
|
mDragApplicationHandle.name = "drag";
|
|
mDragApplicationHandle.dispatchingTimeoutNanos =
|
|
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
|
|
|
|
mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null);
|
|
mDragWindowHandle.name = "drag";
|
|
mDragWindowHandle.inputChannel = mServerChannel;
|
|
mDragWindowHandle.layer = getDragLayerLw();
|
|
mDragWindowHandle.layoutParamsFlags = 0;
|
|
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
|
|
mDragWindowHandle.dispatchingTimeoutNanos =
|
|
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
|
|
mDragWindowHandle.visible = true;
|
|
mDragWindowHandle.canReceiveKeys = false;
|
|
mDragWindowHandle.hasFocus = true;
|
|
mDragWindowHandle.hasWallpaper = false;
|
|
mDragWindowHandle.paused = false;
|
|
mDragWindowHandle.ownerPid = Process.myPid();
|
|
mDragWindowHandle.ownerUid = Process.myUid();
|
|
mDragWindowHandle.inputFeatures = 0;
|
|
mDragWindowHandle.scaleFactor = 1.0f;
|
|
|
|
// The drag window cannot receive new touches.
|
|
mDragWindowHandle.touchableRegion.setEmpty();
|
|
|
|
// The drag window covers the entire display
|
|
mDragWindowHandle.frameLeft = 0;
|
|
mDragWindowHandle.frameTop = 0;
|
|
mDragWindowHandle.frameRight = mService.mCurDisplayWidth;
|
|
mDragWindowHandle.frameBottom = mService.mCurDisplayHeight;
|
|
|
|
// Pause rotations before a drag.
|
|
if (WindowManagerService.DEBUG_ORIENTATION) {
|
|
Slog.d(WindowManagerService.TAG, "Pausing rotation during drag");
|
|
}
|
|
mService.pauseRotationLocked();
|
|
}
|
|
}
|
|
|
|
void unregister() {
|
|
if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "unregistering drag input channel");
|
|
if (mClientChannel == null) {
|
|
Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel");
|
|
} else {
|
|
mService.mInputManager.unregisterInputChannel(mServerChannel);
|
|
InputQueue.unregisterInputChannel(mClientChannel);
|
|
mClientChannel.dispose();
|
|
mServerChannel.dispose();
|
|
mClientChannel = null;
|
|
mServerChannel = null;
|
|
|
|
mDragWindowHandle = null;
|
|
mDragApplicationHandle = null;
|
|
|
|
// Resume rotations after a drag.
|
|
if (WindowManagerService.DEBUG_ORIENTATION) {
|
|
Slog.d(WindowManagerService.TAG, "Resuming rotation after drag");
|
|
}
|
|
mService.resumeRotationLocked();
|
|
}
|
|
}
|
|
|
|
int getDragLayerLw() {
|
|
return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
|
|
* WindowManagerService.TYPE_LAYER_MULTIPLIER
|
|
+ WindowManagerService.TYPE_LAYER_OFFSET;
|
|
}
|
|
|
|
/* call out to each visible window/session informing it about the drag
|
|
*/
|
|
void broadcastDragStartedLw(final float touchX, final float touchY) {
|
|
// Cache a base-class instance of the clip metadata so that parceling
|
|
// works correctly in calling out to the apps.
|
|
mDataDescription = (mData != null) ? mData.getDescription() : null;
|
|
mNotifiedWindows.clear();
|
|
mDragInProgress = true;
|
|
|
|
if (WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
|
|
}
|
|
|
|
final int N = mService.mWindows.size();
|
|
for (int i = 0; i < N; i++) {
|
|
sendDragStartedLw(mService.mWindows.get(i), touchX, touchY, mDataDescription);
|
|
}
|
|
}
|
|
|
|
/* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
|
|
* designated window is potentially a drop recipient. There are race situations
|
|
* around DRAG_ENDED broadcast, so we make sure that once we've declared that
|
|
* the drag has ended, we never send out another DRAG_STARTED for this drag action.
|
|
*
|
|
* This method clones the 'event' parameter if it's being delivered to the same
|
|
* process, so it's safe for the caller to call recycle() on the event afterwards.
|
|
*/
|
|
private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
|
|
ClipDescription desc) {
|
|
// Don't actually send the event if the drag is supposed to be pinned
|
|
// to the originating window but 'newWin' is not that window.
|
|
if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
|
|
final IBinder winBinder = newWin.mClient.asBinder();
|
|
if (winBinder != mLocalWin) {
|
|
if (WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "Not dispatching local DRAG_STARTED to " + newWin);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mDragInProgress && newWin.isPotentialDragTarget()) {
|
|
DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
|
|
touchX, touchY, null, desc, null, false);
|
|
try {
|
|
newWin.mClient.dispatchDragEvent(event);
|
|
// track each window that we've notified that the drag is starting
|
|
mNotifiedWindows.add(newWin);
|
|
} catch (RemoteException e) {
|
|
Slog.w(WindowManagerService.TAG, "Unable to drag-start window " + newWin);
|
|
} finally {
|
|
// if the callee was local, the dispatch has already recycled the event
|
|
if (Process.myPid() != newWin.mSession.mPid) {
|
|
event.recycle();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* helper - construct and send a DRAG_STARTED event only if the window has not
|
|
* previously been notified, i.e. it became visible after the drag operation
|
|
* was begun. This is a rare case.
|
|
*/
|
|
void sendDragStartedIfNeededLw(WindowState newWin) {
|
|
if (mDragInProgress) {
|
|
// If we have sent the drag-started, we needn't do so again
|
|
for (WindowState ws : mNotifiedWindows) {
|
|
if (ws == newWin) {
|
|
return;
|
|
}
|
|
}
|
|
if (WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "need to send DRAG_STARTED to new window " + newWin);
|
|
}
|
|
sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
|
|
}
|
|
}
|
|
|
|
void broadcastDragEndedLw() {
|
|
if (WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED");
|
|
}
|
|
DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
|
|
0, 0, null, null, null, mDragResult);
|
|
for (WindowState ws: mNotifiedWindows) {
|
|
try {
|
|
ws.mClient.dispatchDragEvent(evt);
|
|
} catch (RemoteException e) {
|
|
Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws);
|
|
}
|
|
}
|
|
mNotifiedWindows.clear();
|
|
mDragInProgress = false;
|
|
evt.recycle();
|
|
}
|
|
|
|
void endDragLw() {
|
|
mService.mDragState.broadcastDragEndedLw();
|
|
|
|
// stop intercepting input
|
|
mService.mDragState.unregister();
|
|
mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
|
|
|
|
// free our resources and drop all the object references
|
|
mService.mDragState.reset();
|
|
mService.mDragState = null;
|
|
}
|
|
|
|
void notifyMoveLw(float x, float y) {
|
|
final int myPid = Process.myPid();
|
|
|
|
// Move the surface to the given touch
|
|
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw");
|
|
Surface.openTransaction();
|
|
try {
|
|
mSurface.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
|
|
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG "
|
|
+ mSurface + ": pos=(" +
|
|
(int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
|
|
} finally {
|
|
Surface.closeTransaction();
|
|
if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw");
|
|
}
|
|
|
|
// Tell the affected window
|
|
WindowState touchedWin = getTouchedWinAtPointLw(x, y);
|
|
if (touchedWin == null) {
|
|
if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "No touched win at x=" + x + " y=" + y);
|
|
return;
|
|
}
|
|
if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
|
|
final IBinder touchedBinder = touchedWin.mClient.asBinder();
|
|
if (touchedBinder != mLocalWin) {
|
|
// This drag is pinned only to the originating window, but the drag
|
|
// point is outside that window. Pretend it's over empty space.
|
|
touchedWin = null;
|
|
}
|
|
}
|
|
try {
|
|
// have we dragged over a new window?
|
|
if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
|
|
if (WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "sending DRAG_EXITED to " + mTargetWindow);
|
|
}
|
|
// force DRAG_EXITED_EVENT if appropriate
|
|
DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
|
|
x, y, null, null, null, false);
|
|
mTargetWindow.mClient.dispatchDragEvent(evt);
|
|
if (myPid != mTargetWindow.mSession.mPid) {
|
|
evt.recycle();
|
|
}
|
|
}
|
|
if (touchedWin != null) {
|
|
if (false && WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "sending DRAG_LOCATION to " + touchedWin);
|
|
}
|
|
DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
|
|
x, y, null, null, null, false);
|
|
touchedWin.mClient.dispatchDragEvent(evt);
|
|
if (myPid != touchedWin.mSession.mPid) {
|
|
evt.recycle();
|
|
}
|
|
}
|
|
} catch (RemoteException e) {
|
|
Slog.w(WindowManagerService.TAG, "can't send drag notification to windows");
|
|
}
|
|
mTargetWindow = touchedWin;
|
|
}
|
|
|
|
// Tell the drop target about the data. Returns 'true' if we can immediately
|
|
// dispatch the global drag-ended message, 'false' if we need to wait for a
|
|
// result from the recipient.
|
|
boolean notifyDropLw(float x, float y) {
|
|
WindowState touchedWin = getTouchedWinAtPointLw(x, y);
|
|
if (touchedWin == null) {
|
|
// "drop" outside a valid window -- no recipient to apply a
|
|
// timeout to, and we can send the drag-ended message immediately.
|
|
mDragResult = false;
|
|
return true;
|
|
}
|
|
|
|
if (WindowManagerService.DEBUG_DRAG) {
|
|
Slog.d(WindowManagerService.TAG, "sending DROP to " + touchedWin);
|
|
}
|
|
final int myPid = Process.myPid();
|
|
final IBinder token = touchedWin.mClient.asBinder();
|
|
DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
|
|
null, null, mData, false);
|
|
try {
|
|
touchedWin.mClient.dispatchDragEvent(evt);
|
|
|
|
// 5 second timeout for this window to respond to the drop
|
|
mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
|
|
Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
|
|
mService.mH.sendMessageDelayed(msg, 5000);
|
|
} catch (RemoteException e) {
|
|
Slog.w(WindowManagerService.TAG, "can't send drop notification to win " + touchedWin);
|
|
return true;
|
|
} finally {
|
|
if (myPid != touchedWin.mSession.mPid) {
|
|
evt.recycle();
|
|
}
|
|
}
|
|
mToken = token;
|
|
return false;
|
|
}
|
|
|
|
// Find the visible, touch-deliverable window under the given point
|
|
private WindowState getTouchedWinAtPointLw(float xf, float yf) {
|
|
WindowState touchedWin = null;
|
|
final int x = (int) xf;
|
|
final int y = (int) yf;
|
|
final ArrayList<WindowState> windows = mService.mWindows;
|
|
final int N = windows.size();
|
|
for (int i = N - 1; i >= 0; i--) {
|
|
WindowState child = windows.get(i);
|
|
final int flags = child.mAttrs.flags;
|
|
if (!child.isVisibleLw()) {
|
|
// not visible == don't tell about drags
|
|
continue;
|
|
}
|
|
if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
|
|
// not touchable == don't tell about drags
|
|
continue;
|
|
}
|
|
|
|
child.getTouchableRegion(mTmpRegion);
|
|
|
|
final int touchFlags = flags &
|
|
(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
|
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
|
|
if (mTmpRegion.contains(x, y) || touchFlags == 0) {
|
|
// Found it
|
|
touchedWin = child;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return touchedWin;
|
|
}
|
|
|
|
private static DragEvent obtainDragEvent(WindowState win, int action,
|
|
float x, float y, Object localState,
|
|
ClipDescription description, ClipData data, boolean result) {
|
|
float winX = x - win.mFrame.left;
|
|
float winY = y - win.mFrame.top;
|
|
if (win.mEnforceSizeCompat) {
|
|
winX *= win.mGlobalScale;
|
|
winY *= win.mGlobalScale;
|
|
}
|
|
return DragEvent.obtain(action, winX, winY, localState, description, data, result);
|
|
}
|
|
} |