blob: b7eab085f5e34406441356ce8c04231ab5e517b2 [file] [log] [blame]
/*
* Copyright (C) 2019 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 static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowStateProto.IDENTIFIER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
/**
* Keeps track of embedded windows.
*
* If the embedded window does not receive input then Window Manager does not keep track of it.
* But if they do receive input, we keep track of the calling PID to blame the right app and
* the host window to send pointerDownOutsideFocus.
*/
class EmbeddedWindowController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
/* maps input token to an embedded window */
private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
private ArrayMap<IBinder /*input transfer token */, EmbeddedWindow>
mWindowsByInputTransferToken = new ArrayMap<>();
private ArrayMap<IBinder /*window token*/, EmbeddedWindow> mWindowsByWindowToken =
new ArrayMap<>();
private final Object mGlobalLock;
private final ActivityTaskManagerService mAtmService;
EmbeddedWindowController(ActivityTaskManagerService atmService) {
mAtmService = atmService;
mGlobalLock = atmService.getGlobalLock();
}
/**
* Adds a new embedded window.
*
* @param inputToken input channel token passed in by the embedding process when it requests
* the server to add an input channel to the embedded surface.
* @param window An {@link EmbeddedWindow} object to add to this controller.
*/
void add(IBinder inputToken, EmbeddedWindow window) {
try {
mWindows.put(inputToken, window);
final IBinder inputTransferToken = window.getInputTransferToken();
mWindowsByInputTransferToken.put(inputTransferToken, window);
mWindowsByWindowToken.put(window.getWindowToken(), window);
updateProcessController(window);
window.mClient.linkToDeath(()-> {
synchronized (mGlobalLock) {
mWindows.remove(inputToken);
mWindowsByInputTransferToken.remove(inputTransferToken);
}
}, 0);
} catch (RemoteException e) {
// The caller has died, remove from the map
mWindows.remove(inputToken);
}
}
/**
* Track the host activity in the embedding process so we can determine if the
* process is currently showing any UI to the user.
*/
private void updateProcessController(EmbeddedWindow window) {
if (window.mHostActivityRecord == null) {
return;
}
final WindowProcessController processController =
mAtmService.getProcessController(window.mOwnerPid, window.mOwnerUid);
if (processController == null) {
Slog.w(TAG, "Could not find the embedding process.");
} else {
processController.addHostActivity(window.mHostActivityRecord);
}
}
void remove(IBinder client) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
EmbeddedWindow ew = mWindows.valueAt(i);
if (ew.mClient == client) {
mWindows.removeAt(i).onRemoved();
mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
mWindowsByWindowToken.remove(ew.getWindowToken());
return;
}
}
}
void onWindowRemoved(WindowState host) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
EmbeddedWindow ew = mWindows.valueAt(i);
if (ew.mHostWindowState == host) {
mWindows.removeAt(i).onRemoved();
mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
mWindowsByWindowToken.remove(ew.getWindowToken());
}
}
}
EmbeddedWindow get(IBinder inputToken) {
return mWindows.get(inputToken);
}
EmbeddedWindow getByInputTransferToken(IBinder inputTransferToken) {
return mWindowsByInputTransferToken.get(inputTransferToken);
}
EmbeddedWindow getByWindowToken(IBinder windowToken) {
return mWindowsByWindowToken.get(windowToken);
}
static class EmbeddedWindow implements InputTarget {
final IBinder mClient;
@Nullable final WindowState mHostWindowState;
@Nullable final ActivityRecord mHostActivityRecord;
final String mName;
final int mOwnerUid;
final int mOwnerPid;
final WindowManagerService mWmService;
final int mDisplayId;
public Session mSession;
InputChannel mInputChannel;
final int mWindowType;
/**
* A unique token associated with the embedded window that can be used by the host window
* to request focus transfer and gesture transfer to the embedded. This is not the input
* token since we don't want to give clients access to each others input token.
*/
private final IBinder mInputTransferToken;
private boolean mIsFocusable;
/**
* @param session calling session to check ownership of the window
* @param clientToken client token used to clean up the map if the embedding process dies
* @param hostWindowState input channel token belonging to the host window. This is needed
* to handle input callbacks to wm. It's used when raising ANR and
* when the user taps out side of the focused region on screen. This
* can be null if there is no host window.
* @param ownerUid calling uid
* @param ownerPid calling pid used for anr blaming
* @param windowType to forward to input
* @param displayId used for focus requests
*/
EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken,
WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
int displayId, IBinder inputTransferToken, String inputHandleName,
boolean isFocusable) {
mSession = session;
mWmService = service;
mClient = clientToken;
mHostWindowState = hostWindowState;
mHostActivityRecord = (mHostWindowState != null) ? mHostWindowState.mActivityRecord
: null;
mOwnerUid = ownerUid;
mOwnerPid = ownerPid;
mWindowType = windowType;
mDisplayId = displayId;
mInputTransferToken = inputTransferToken;
final String hostWindowName =
(mHostWindowState != null) ? "-" + mHostWindowState.getWindowTag().toString()
: "";
mIsFocusable = isFocusable;
mName = "Embedded{" + inputHandleName + hostWindowName + "}";
}
@Override
public String toString() {
return mName;
}
InputApplicationHandle getApplicationHandle() {
if (mHostWindowState == null
|| mHostWindowState.mInputWindowHandle.getInputApplicationHandle() == null) {
return null;
}
return new InputApplicationHandle(
mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
}
void openInputChannel(@NonNull InputChannel outInputChannel) {
final String name = toString();
mInputChannel = mWmService.mInputManager.createInputChannel(name);
mInputChannel.copyTo(outInputChannel);
}
void onRemoved() {
if (mInputChannel != null) {
mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
mInputChannel.dispose();
mInputChannel = null;
}
if (mHostActivityRecord != null) {
final WindowProcessController wpc =
mWmService.mAtmService.getProcessController(mOwnerPid, mOwnerUid);
if (wpc != null) {
wpc.removeHostActivity(mHostActivityRecord);
}
}
}
@Override
public WindowState getWindowState() {
return mHostWindowState;
}
@Override
public int getDisplayId() {
return mDisplayId;
}
@Override
public DisplayContent getDisplayContent() {
return mWmService.mRoot.getDisplayContent(getDisplayId());
}
public IBinder getWindowToken() {
return mClient;
}
@Override
public int getPid() {
return mOwnerPid;
}
@Override
public int getUid() {
return mOwnerUid;
}
IBinder getInputTransferToken() {
return mInputTransferToken;
}
IBinder getInputChannelToken() {
if (mInputChannel != null) {
return mInputChannel.getToken();
}
return null;
}
void setIsFocusable(boolean isFocusable) {
mIsFocusable = isFocusable;
}
/**
* When an embedded window is touched when it's not currently focus, we need to switch
* focus to that embedded window unless the embedded window was marked as not focusable.
*/
@Override
public boolean receiveFocusFromTapOutside() {
return mIsFocusable;
}
private void handleTap(boolean grantFocus) {
if (mInputChannel != null) {
if (mHostWindowState != null) {
// Use null session since this is being granted by system server and doesn't
// require the host session to be passed in
mWmService.grantEmbeddedWindowFocus(null, mHostWindowState.mClient,
mInputTransferToken, grantFocus);
if (grantFocus) {
// If granting focus to the embedded when tapped, we need to ensure the host
// gains focus as well or the transfer won't take effect since it requires
// the host to transfer the focus to the embedded.
mHostWindowState.handleTapOutsideFocusInsideSelf();
}
} else {
mWmService.grantEmbeddedWindowFocus(mSession, mInputTransferToken, grantFocus);
}
}
}
@Override
public void handleTapOutsideFocusOutsideSelf() {
handleTap(false);
}
@Override
public void handleTapOutsideFocusInsideSelf() {
handleTap(true);
}
@Override
public boolean shouldControlIme() {
return mHostWindowState != null;
}
@Override
public boolean canScreenshotIme() {
return true;
}
@Override
public InsetsControlTarget getImeControlTarget() {
if (mHostWindowState != null) {
return mHostWindowState.getImeControlTarget();
}
return mWmService.getDefaultDisplayContentLocked().mRemoteInsetsControlTarget;
}
@Override
public boolean isInputMethodClientFocus(int uid, int pid) {
return uid == mOwnerUid && pid == mOwnerPid;
}
@Override
public ActivityRecord getActivityRecord() {
return mHostActivityRecord;
}
@Override
public void dumpProto(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
final long token = proto.start(fieldId);
final long token2 = proto.start(IDENTIFIER);
proto.write(HASH_CODE, System.identityHashCode(this));
proto.write(TITLE, "EmbeddedWindow");
proto.end(token2);
proto.end(token);
}
}
}