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.View; 30 import android.view.ViewGroup; 31 import android.view.ViewTreeObserver; 32 import android.view.WindowManager; 33 34 import com.android.internal.policy.PhoneWindow; 35 36 /** 37 * Window of type {@code LayoutParams.TYPE_INPUT_METHOD_DIALOG} for drawing 38 * Handwriting Ink on screen. 39 * @hide 40 */ 41 final class InkWindow extends PhoneWindow { 42 43 private final WindowManager mWindowManager; 44 private boolean mIsViewAdded; 45 private View mInkView; 46 private InkVisibilityListener mInkViewVisibilityListener; 47 private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener; 48 InkWindow(@onNull Context context)49 public InkWindow(@NonNull Context context) { 50 super(context); 51 52 setType(LayoutParams.TYPE_INPUT_METHOD); 53 final LayoutParams attrs = getAttributes(); 54 attrs.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 55 attrs.setFitInsetsTypes(0); 56 // TODO(b/210039666): use INPUT_FEATURE_NO_INPUT_CHANNEL once b/216179339 is fixed. 57 setAttributes(attrs); 58 // Ink window is not touchable with finger. 59 addFlags(FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_TOUCHABLE 60 | FLAG_NOT_FOCUSABLE); 61 setBackgroundDrawableResource(android.R.color.transparent); 62 setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 63 mWindowManager = context.getSystemService(WindowManager.class); 64 } 65 66 /** 67 * Initialize InkWindow if we only want to create and draw surface but not show it. 68 */ initOnly()69 void initOnly() { 70 show(true /* keepInvisible */); 71 } 72 73 /** 74 * Method to show InkWindow on screen. 75 * Emulates internal behavior similar to Dialog.show(). 76 */ show()77 void show() { 78 show(false /* keepInvisible */); 79 } 80 show(boolean keepInvisible)81 private void show(boolean keepInvisible) { 82 if (getDecorView() == null) { 83 Slog.i(InputMethodService.TAG, "DecorView is not set for InkWindow. show() failed."); 84 return; 85 } 86 getDecorView().setVisibility(keepInvisible ? View.INVISIBLE : View.VISIBLE); 87 if (!mIsViewAdded) { 88 mWindowManager.addView(getDecorView(), getAttributes()); 89 mIsViewAdded = true; 90 } 91 } 92 93 /** 94 * Method to hide InkWindow from screen. 95 * Emulates internal behavior similar to Dialog.hide(). 96 * @param remove set {@code true} to remove InkWindow surface completely. 97 */ hide(boolean remove)98 void hide(boolean remove) { 99 if (getDecorView() != null) { 100 getDecorView().setVisibility(remove ? View.GONE : View.INVISIBLE); 101 } 102 //TODO(b/210039666): remove window from WM after a delay. Delay amount TBD. 103 } 104 setToken(@onNull IBinder token)105 void setToken(@NonNull IBinder token) { 106 WindowManager.LayoutParams lp = getAttributes(); 107 lp.token = token; 108 setAttributes(lp); 109 } 110 111 @Override addContentView(View view, ViewGroup.LayoutParams params)112 public void addContentView(View view, ViewGroup.LayoutParams params) { 113 if (mInkView == null) { 114 mInkView = view; 115 } else if (mInkView != view) { 116 throw new IllegalStateException("Only one Child Inking view is permitted."); 117 } 118 super.addContentView(view, params); 119 initInkViewVisibilityListener(); 120 } 121 122 @Override setContentView(View view, ViewGroup.LayoutParams params)123 public void setContentView(View view, ViewGroup.LayoutParams params) { 124 mInkView = view; 125 super.setContentView(view, params); 126 initInkViewVisibilityListener(); 127 } 128 129 @Override setContentView(View view)130 public void setContentView(View view) { 131 mInkView = view; 132 super.setContentView(view); 133 initInkViewVisibilityListener(); 134 } 135 136 @Override clearContentView()137 public void clearContentView() { 138 if (mGlobalLayoutListener != null && mInkView != null) { 139 mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener); 140 } 141 mGlobalLayoutListener = null; 142 mInkView = null; 143 super.clearContentView(); 144 } 145 146 /** 147 * Listener used by InkWindow to time the dispatching of {@link MotionEvent}s to Ink view, once 148 * it is visible to user. 149 */ 150 interface InkVisibilityListener { onInkViewVisible()151 void onInkViewVisible(); 152 } 153 setInkViewVisibilityListener(InkVisibilityListener listener)154 void setInkViewVisibilityListener(InkVisibilityListener listener) { 155 mInkViewVisibilityListener = listener; 156 initInkViewVisibilityListener(); 157 } 158 initInkViewVisibilityListener()159 void initInkViewVisibilityListener() { 160 if (mInkView == null || mInkViewVisibilityListener == null 161 || mGlobalLayoutListener != null) { 162 return; 163 } 164 mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { 165 @Override 166 public void onGlobalLayout() { 167 if (mInkView.isVisibleToUser()) { 168 if (mInkViewVisibilityListener != null) { 169 mInkViewVisibilityListener.onInkViewVisible(); 170 } 171 mInkView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 172 mGlobalLayoutListener = null; 173 } 174 } 175 }; 176 mInkView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener); 177 } 178 isInkViewVisible()179 boolean isInkViewVisible() { 180 return getDecorView().getVisibility() == View.VISIBLE 181 && mInkView != null && mInkView.isVisibleToUser(); 182 } 183 } 184