blob: 1b8d925ec1cdd6b84097ac6d67b7c452b511d001 [file] [log] [blame]
Aurimas Liutikasdc3f8852024-07-11 10:07:48 -07001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.inputmethodservice;
18
19import static android.view.WindowManager.LayoutParams;
20import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
21import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
22import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
23import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
24
25import android.annotation.NonNull;
26import android.content.Context;
27import android.os.IBinder;
28import android.util.Slog;
29import android.view.MotionEvent;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.ViewRootImpl;
33import android.view.ViewTreeObserver;
34import android.view.WindowManager;
35
36import com.android.internal.policy.PhoneWindow;
37
38import java.util.Objects;
39
40/**
41 * Window of type {@code LayoutParams.TYPE_INPUT_METHOD_DIALOG} for drawing
42 * Handwriting Ink on screen.
43 * @hide
44 */
45final class InkWindow extends PhoneWindow {
46
47 private final WindowManager mWindowManager;
48 private boolean mIsViewAdded;
49 private View mInkView;
50 private InkVisibilityListener mInkViewVisibilityListener;
51 private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener;
52
53 public InkWindow(@NonNull Context context) {
54 super(context);
55
56 setType(LayoutParams.TYPE_INPUT_METHOD);
57 final LayoutParams attrs = getAttributes();
58 attrs.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
59 attrs.setFitInsetsTypes(0);
60 // disable window animations.
61 // TODO(b/253477462): replace with API when available
62 attrs.windowAnimations = -1;
63 // TODO(b/210039666): use INPUT_FEATURE_NO_INPUT_CHANNEL once b/216179339 is fixed.
64 setAttributes(attrs);
65 // Ink window is not touchable with finger.
66 addFlags(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_TOUCHABLE
67 | FLAG_NOT_FOCUSABLE);
68 setBackgroundDrawableResource(android.R.color.transparent);
69 setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
70 mWindowManager = context.getSystemService(WindowManager.class);
71 }
72
73 /**
74 * Initialize InkWindow if we only want to create and draw surface but not show it.
75 */
76 void initOnly() {
77 show(true /* keepInvisible */);
78 }
79
80 /**
81 * Method to show InkWindow on screen.
82 * Emulates internal behavior similar to Dialog.show().
83 */
84 void show() {
85 show(false /* keepInvisible */);
86 }
87
88 private void show(boolean keepInvisible) {
89 if (getDecorView() == null) {
90 Slog.i(InputMethodService.TAG, "DecorView is not set for InkWindow. show() failed.");
91 return;
92 }
93 getDecorView().setVisibility(keepInvisible ? View.INVISIBLE : View.VISIBLE);
94 if (!mIsViewAdded) {
95 mWindowManager.addView(getDecorView(), getAttributes());
96 mIsViewAdded = true;
97 }
98 }
99
100 /**
101 * Method to hide InkWindow from screen.
102 * Emulates internal behavior similar to Dialog.hide().
103 * @param remove set {@code true} to remove InkWindow surface completely.
104 */
105 void hide(boolean remove) {
106 if (getDecorView() != null) {
107 if (remove) {
108 mWindowManager.removeViewImmediate(getDecorView());
109 } else {
110 getDecorView().setVisibility(View.INVISIBLE);
111 }
112 }
113 }
114
115 void setToken(@NonNull IBinder token) {
116 WindowManager.LayoutParams lp = getAttributes();
117 lp.token = token;
118 setAttributes(lp);
119 }
120
121 @Override
122 public void addContentView(View view, ViewGroup.LayoutParams params) {
123 if (mInkView == null) {
124 mInkView = view;
125 } else if (mInkView != view) {
126 throw new IllegalStateException("Only one Child Inking view is permitted.");
127 }
128 super.addContentView(view, params);
129 initInkViewVisibilityListener();
130 }
131
132 @Override
133 public void setContentView(View view, ViewGroup.LayoutParams params) {
134 mInkView = view;
135 super.setContentView(view, params);
136 initInkViewVisibilityListener();
137 }
138
139 @Override
140 public void setContentView(View view) {
141 mInkView = view;
142 super.setContentView(view);
143 initInkViewVisibilityListener();
144 }
145
146 @Override
147 public void clearContentView() {
148 if (mGlobalLayoutListener != null && mInkView != null) {
149 mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
150 }
151 mGlobalLayoutListener = null;
152 mInkView = null;
153 super.clearContentView();
154 }
155
156 /**
157 * Listener used by InkWindow to time the dispatching of {@link MotionEvent}s to Ink view, once
158 * it is visible to user.
159 */
160 interface InkVisibilityListener {
161 void onInkViewVisible();
162 }
163
164 void setInkViewVisibilityListener(InkVisibilityListener listener) {
165 mInkViewVisibilityListener = listener;
166 initInkViewVisibilityListener();
167 }
168
169 void initInkViewVisibilityListener() {
170 if (mInkView == null || mInkViewVisibilityListener == null
171 || mGlobalLayoutListener != null) {
172 return;
173 }
174 mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
175 @Override
176 public void onGlobalLayout() {
177 if (mInkView == null) {
178 return;
179 }
180 if (mInkView.isVisibleToUser()) {
181 if (mInkViewVisibilityListener != null) {
182 mInkViewVisibilityListener.onInkViewVisible();
183 }
184 mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
185 mGlobalLayoutListener = null;
186 }
187 }
188 };
189 mInkView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
190 }
191
192 boolean isInkViewVisible() {
193 return getDecorView().getVisibility() == View.VISIBLE
194 && mInkView != null && mInkView.isVisibleToUser();
195 }
196
197 void dispatchHandwritingEvent(@NonNull MotionEvent event) {
198 final View decor = getDecorView();
199 Objects.requireNonNull(decor);
200 final ViewRootImpl viewRoot = decor.getViewRootImpl();
201 Objects.requireNonNull(viewRoot);
202 // The view root will own the event that we enqueue, so provide a copy of the event.
203 viewRoot.enqueueInputEvent(MotionEvent.obtain(event));
204 }
205}