1 /* 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 17 package android.inputmethodservice; 18 19 import static android.view.WindowManager.LayoutParams; 20 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 21 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 22 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 23 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 24 25 import android.annotation.NonNull; 26 import android.content.Context; 27 import android.os.IBinder; 28 import android.util.Slog; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.ViewRootImpl; 33 import android.view.ViewTreeObserver; 34 import android.view.WindowManager; 35 36 import com.android.internal.policy.PhoneWindow; 37 38 import 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 */ 45 final 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 InkWindow(@onNull Context context)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 */ initOnly()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 */ show()84 void show() { 85 show(false /* keepInvisible */); 86 } 87 show(boolean keepInvisible)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 */ hide(boolean remove)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 setToken(@onNull IBinder token)115 void setToken(@NonNull IBinder token) { 116 WindowManager.LayoutParams lp = getAttributes(); 117 lp.token = token; 118 setAttributes(lp); 119 } 120 121 @Override addContentView(View view, ViewGroup.LayoutParams params)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 setContentView(View view, ViewGroup.LayoutParams params)133 public void setContentView(View view, ViewGroup.LayoutParams params) { 134 mInkView = view; 135 super.setContentView(view, params); 136 initInkViewVisibilityListener(); 137 } 138 139 @Override setContentView(View view)140 public void setContentView(View view) { 141 mInkView = view; 142 super.setContentView(view); 143 initInkViewVisibilityListener(); 144 } 145 146 @Override clearContentView()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 { onInkViewVisible()161 void onInkViewVisible(); 162 } 163 setInkViewVisibilityListener(InkVisibilityListener listener)164 void setInkViewVisibilityListener(InkVisibilityListener listener) { 165 mInkViewVisibilityListener = listener; 166 initInkViewVisibilityListener(); 167 } 168 initInkViewVisibilityListener()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 isInkViewVisible()192 boolean isInkViewVisible() { 193 return getDecorView().getVisibility() == View.VISIBLE 194 && mInkView != null && mInkView.isVisibleToUser(); 195 } 196 dispatchHandwritingEvent(@onNull MotionEvent event)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 } 206