1 /* 2 * Copyright (C) 2020 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.view.inputmethod.cts.util; 18 19 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 20 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 21 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 22 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 23 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; 24 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 25 26 import android.app.Service; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.graphics.Color; 30 import android.graphics.PixelFormat; 31 import android.graphics.Point; 32 import android.graphics.drawable.ColorDrawable; 33 import android.hardware.display.DisplayManager; 34 import android.os.Binder; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.IBinder; 38 import android.util.Log; 39 import android.view.Display; 40 import android.view.MotionEvent; 41 import android.view.ViewTreeObserver; 42 import android.view.WindowManager; 43 import android.widget.EditText; 44 45 import androidx.annotation.AnyThread; 46 import androidx.annotation.MainThread; 47 import androidx.annotation.Nullable; 48 import androidx.annotation.UiThread; 49 50 import com.android.compatibility.common.util.UserHelper; 51 52 import java.util.concurrent.CountDownLatch; 53 import java.util.concurrent.TimeUnit; 54 import java.util.concurrent.atomic.AtomicBoolean; 55 56 /** 57 * A Service class to create popup window with edit text by handler thread. 58 * For verifying IME focus handle between windows on different UI thread. 59 */ 60 public class WindowFocusHandleService extends Service { 61 private @Nullable static WindowFocusHandleService sInstance = null; 62 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); 63 private static final String TAG = WindowFocusHandleService.class.getSimpleName(); 64 65 private EditText mPopupTextView; 66 private Handler mThreadHandler; 67 private CountDownLatch mUiThreadSignal; 68 69 private final UserHelper mUserHelper = new UserHelper(); 70 71 @Override onCreate()72 public void onCreate() { 73 super.onCreate(); 74 // Create a popup text view with different UI thread. 75 final HandlerThread localThread = new HandlerThread("TestThreadHandler"); 76 localThread.start(); 77 mThreadHandler = new Handler(localThread.getLooper()); 78 mThreadHandler.post(() -> mPopupTextView = 79 createPopupTextView(new Point(150, 150), mUserHelper.getMainDisplayId())); 80 } 81 getInstance()82 public @Nullable static WindowFocusHandleService getInstance() { 83 return sInstance; 84 } 85 86 @Override onBind(Intent intent)87 public IBinder onBind(Intent intent) { 88 sInstance = this; 89 return new Binder(); 90 } 91 92 @Override onUnbind(Intent intent)93 public boolean onUnbind(Intent intent) { 94 sInstance = null; 95 return true; 96 } 97 98 @UiThread createPopupTextView(Point pos, int displayId)99 private EditText createPopupTextView(Point pos, int displayId) { 100 final Context windowContext = createWindowContext(displayId); 101 final WindowManager wm = windowContext.getSystemService(WindowManager.class); 102 final EditText editText = new EditText(windowContext) { 103 @Override 104 public void onWindowFocusChanged(boolean hasWindowFocus) { 105 if (Log.isLoggable(TAG, Log.VERBOSE)) { 106 Log.v(TAG, "onWindowFocusChanged for view=" + this 107 + ", hasWindowfocus: " + hasWindowFocus); 108 } 109 } 110 111 @Override 112 public boolean onCheckIsTextEditor() { 113 super.onCheckIsTextEditor(); 114 if (getHandler() != null && mUiThreadSignal != null) { 115 if (Thread.currentThread().getId() 116 == getHandler().getLooper().getThread().getId()) { 117 mUiThreadSignal.countDown(); 118 } 119 } 120 return true; 121 } 122 }; 123 editText.setOnFocusChangeListener((v, hasFocus) -> { 124 if (v == editText) { 125 WindowManager.LayoutParams params = 126 (WindowManager.LayoutParams) editText.getLayoutParams(); 127 if (hasFocus) { 128 params.flags &= ~FLAG_NOT_FOCUSABLE; 129 params.flags |= FLAG_LAYOUT_IN_SCREEN 130 | FLAG_WATCH_OUTSIDE_TOUCH 131 | FLAG_NOT_TOUCH_MODAL; 132 wm.updateViewLayout(editText, params); 133 } 134 } 135 }); 136 editText.setOnTouchListener((v, event) -> { 137 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { 138 WindowManager.LayoutParams params = 139 (WindowManager.LayoutParams) editText.getLayoutParams(); 140 if ((params.flags & FLAG_NOT_FOCUSABLE) == 0) { 141 params.flags |= FLAG_NOT_FOCUSABLE; 142 wm.updateViewLayout(editText, params); 143 } 144 } 145 return false; 146 }); 147 editText.setBackground(new ColorDrawable(Color.CYAN)); 148 149 WindowManager.LayoutParams params = new WindowManager.LayoutParams( 150 150, 150, pos.x, pos.y, 151 TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE, 152 PixelFormat.OPAQUE); 153 // Currently SOFT_INPUT_STATE_UNSPECIFIED isn't appropriate for CTS test because there is no 154 // clear spec about how it behaves. In order to make our tests deterministic, currently we 155 // must use SOFT_INPUT_STATE_HIDDEN to make sure soft-keyboard will hide after navigating 156 // forward to next window. 157 // TODO(Bug 77152727): Remove the following code once we define how 158 params.softInputMode = SOFT_INPUT_STATE_HIDDEN; 159 wm.addView(editText, params); 160 return editText; 161 } 162 createWindowContext(int displayId)163 private Context createWindowContext(int displayId) { 164 final Display display = getSystemService(DisplayManager.class).getDisplay(displayId); 165 return createDisplayContext(display).createWindowContext(TYPE_APPLICATION_OVERLAY, 166 null /* options */); 167 } 168 169 @AnyThread getPopupTextView( @ullable AtomicBoolean outPopupTextHasWindowFocusRef)170 public EditText getPopupTextView( 171 @Nullable AtomicBoolean outPopupTextHasWindowFocusRef) throws Exception { 172 if (outPopupTextHasWindowFocusRef != null) { 173 TestUtils.waitOnMainUntil(() -> mPopupTextView != null, 174 TIMEOUT, "PopupTextView should be created"); 175 176 mPopupTextView.post(() -> { 177 final ViewTreeObserver observerForPopupTextView = 178 mPopupTextView.getViewTreeObserver(); 179 observerForPopupTextView.addOnWindowFocusChangeListener((hasFocus) -> 180 outPopupTextHasWindowFocusRef.set(hasFocus)); 181 }); 182 } 183 return mPopupTextView; 184 } 185 186 /** 187 * Tests can set a {@link CountDownLatch} to wait until associated action performed on 188 * UI thread. 189 * 190 * @param uiThreadSignal the {@link CountDownLatch} used to countdown. 191 */ 192 @AnyThread setUiThreadSignal(CountDownLatch uiThreadSignal)193 public void setUiThreadSignal(CountDownLatch uiThreadSignal) { 194 mUiThreadSignal = uiThreadSignal; 195 } 196 197 @MainThread handleReset()198 public void handleReset() { 199 if (mPopupTextView != null) { 200 final WindowManager wm = mPopupTextView.getContext().getSystemService( 201 WindowManager.class); 202 wm.removeView(mPopupTextView); 203 mPopupTextView = null; 204 } 205 } 206 207 @MainThread onDestroy()208 public void onDestroy() { 209 super.onDestroy(); 210 handleReset(); 211 } 212 } 213