1 /* 2 * Copyright (C) 2022 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 com.android.wm.shell.compatui; 18 19 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 20 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 21 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 22 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 23 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 24 25 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 26 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 27 import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.app.TaskInfo; 32 import android.content.Context; 33 import android.content.res.Configuration; 34 import android.graphics.PixelFormat; 35 import android.graphics.Rect; 36 import android.os.Binder; 37 import android.util.Log; 38 import android.view.IWindow; 39 import android.view.SurfaceControl; 40 import android.view.SurfaceControlViewHost; 41 import android.view.SurfaceSession; 42 import android.view.View; 43 import android.view.WindowManager; 44 import android.view.WindowlessWindowManager; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.wm.shell.ShellTaskOrganizer; 48 import com.android.wm.shell.common.DisplayLayout; 49 import com.android.wm.shell.common.SyncTransactionQueue; 50 51 /** 52 * A superclass for all Compat UI {@link WindowlessWindowManager}s that holds shared logic and 53 * exposes general API for {@link CompatUIController}. 54 * 55 * <p>Holds view hierarchy of a root surface and helps to inflate and manage layout. 56 */ 57 public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager { 58 59 protected final int mTaskId; 60 protected Context mContext; 61 62 private final SyncTransactionQueue mSyncQueue; 63 private final int mDisplayId; 64 private Configuration mTaskConfig; 65 private ShellTaskOrganizer.TaskListener mTaskListener; 66 private DisplayLayout mDisplayLayout; 67 private final Rect mStableBounds; 68 69 @NonNull 70 private TaskInfo mTaskInfo; 71 72 /** 73 * Utility class for adding and releasing a View hierarchy for this {@link 74 * WindowlessWindowManager} to {@code mLeash}. 75 */ 76 @Nullable 77 protected SurfaceControlViewHost mViewHost; 78 79 /** 80 * A surface leash to position the layout relative to the task, since we can't set position for 81 * the {@code mViewHost} directly. 82 */ 83 @Nullable 84 protected SurfaceControl mLeash; 85 CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout)86 protected CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo, 87 SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, 88 DisplayLayout displayLayout) { 89 super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */); 90 mTaskInfo = taskInfo; 91 mContext = context; 92 mSyncQueue = syncQueue; 93 mTaskConfig = taskInfo.configuration; 94 mDisplayId = mContext.getDisplayId(); 95 mTaskId = taskInfo.taskId; 96 mTaskListener = taskListener; 97 mDisplayLayout = displayLayout; 98 mStableBounds = new Rect(); 99 mDisplayLayout.getStableBounds(mStableBounds); 100 } 101 102 /** 103 * @return {@code true} if the instance of the specific {@link CompatUIWindowManagerAbstract} 104 * for the current task id needs to be recreated loading the related resources. This happens 105 * if the user switches between Light/Dark mode, if the device is docked/undocked or if the 106 * user switches between multi-window mode to fullscreen where the 107 * {@link ShellTaskOrganizer.TaskListener} implementation is different. 108 */ needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)109 boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { 110 return hasUiModeChanged(mTaskInfo, taskInfo) || hasTaskListenerChanged(taskListener); 111 } 112 113 /** 114 * Returns the z-order of this window which will be passed to the {@link SurfaceControl} once 115 * {@link #attachToParentSurface} is called. 116 * 117 * <p>See {@link SurfaceControl.Transaction#setLayer}. 118 */ getZOrder()119 protected abstract int getZOrder(); 120 121 /** Returns the layout of this window manager. */ getLayout()122 protected abstract @Nullable View getLayout(); 123 124 /** 125 * Inflates and inits the layout of this window manager on to the root surface if both {@code 126 * canShow} and {@link #eligibleToShowLayout} are true. 127 * 128 * <p>Doesn't do anything if layout is not eligible to be shown. 129 * 130 * @param canShow whether the layout is allowed to be shown by the parent controller. 131 * @return whether the layout is eligible to be shown. 132 */ 133 @VisibleForTesting(visibility = PROTECTED) createLayout(boolean canShow)134 public boolean createLayout(boolean canShow) { 135 if (!eligibleToShowLayout()) { 136 return false; 137 } 138 if (!canShow || getLayout() != null) { 139 // Wait until layout should be visible, or layout was already created. 140 return true; 141 } 142 143 if (mViewHost != null) { 144 throw new IllegalStateException( 145 "A UI has already been created with this window manager."); 146 } 147 148 // Construction extracted into separate methods to allow injection for tests. 149 mViewHost = createSurfaceViewHost(); 150 mViewHost.setView(createLayout(), getWindowLayoutParams()); 151 152 updateSurfacePosition(); 153 154 return true; 155 } 156 157 /** Inflates and inits the layout of this window manager. */ createLayout()158 protected abstract View createLayout(); 159 removeLayout()160 protected abstract void removeLayout(); 161 162 /** 163 * Whether the layout is eligible to be shown according to the internal state of the subclass. 164 */ eligibleToShowLayout()165 protected abstract boolean eligibleToShowLayout(); 166 167 @Override setConfiguration(Configuration configuration)168 public void setConfiguration(Configuration configuration) { 169 super.setConfiguration(configuration); 170 mContext = mContext.createConfigurationContext(configuration); 171 } 172 173 @Override getParentSurface(IWindow window, WindowManager.LayoutParams attrs)174 protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { 175 String className = getClass().getSimpleName(); 176 final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) 177 .setContainerLayer() 178 .setName(className + "Leash") 179 .setHidden(false) 180 .setCallsite(className + "#attachToParentSurface"); 181 attachToParentSurface(builder); 182 mLeash = builder.build(); 183 initSurface(mLeash); 184 return mLeash; 185 } 186 getTaskListener()187 protected ShellTaskOrganizer.TaskListener getTaskListener() { 188 return mTaskListener; 189 } 190 191 /** Inits the z-order of the surface. */ initSurface(SurfaceControl leash)192 private void initSurface(SurfaceControl leash) { 193 final int z = getZOrder(); 194 mSyncQueue.runInSync(t -> { 195 if (leash == null || !leash.isValid()) { 196 Log.w(getTag(), "The leash has been released."); 197 return; 198 } 199 t.setLayer(leash, z); 200 }); 201 } 202 203 /** 204 * Called when compat info changed. 205 * 206 * <p>The window manager is released if the layout is no longer eligible to be shown. 207 * 208 * @param canShow whether the layout is allowed to be shown by the parent controller. 209 * @return whether the layout is eligible to be shown. 210 */ 211 @VisibleForTesting(visibility = PROTECTED) updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow)212 public boolean updateCompatInfo(TaskInfo taskInfo, 213 ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { 214 mTaskInfo = taskInfo; 215 final Configuration prevTaskConfig = mTaskConfig; 216 final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener; 217 mTaskConfig = taskInfo.configuration; 218 mTaskListener = taskListener; 219 220 // Update configuration. 221 setConfiguration(mTaskConfig); 222 223 if (!eligibleToShowLayout()) { 224 release(); 225 return false; 226 } 227 228 View layout = getLayout(); 229 if (layout == null || prevTaskListener != taskListener 230 || mTaskConfig.uiMode != prevTaskConfig.uiMode) { 231 // Layout wasn't created yet or TaskListener changed, recreate the layout for new 232 // surface parent. 233 release(); 234 return createLayout(canShow); 235 } 236 237 boolean boundsUpdated = !mTaskConfig.windowConfiguration.getBounds().equals( 238 prevTaskConfig.windowConfiguration.getBounds()); 239 boolean layoutDirectionUpdated = 240 mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection(); 241 if (boundsUpdated || layoutDirectionUpdated) { 242 onParentBoundsChanged(); 243 } 244 245 if (layout != null && layoutDirectionUpdated) { 246 // Update layout for RTL. 247 layout.setLayoutDirection(mTaskConfig.getLayoutDirection()); 248 } 249 250 return true; 251 } 252 253 /** 254 * Updates the visibility of the layout. 255 * 256 * @param canShow whether the layout is allowed to be shown by the parent controller. 257 */ 258 @VisibleForTesting(visibility = PACKAGE) updateVisibility(boolean canShow)259 public void updateVisibility(boolean canShow) { 260 View layout = getLayout(); 261 if (layout == null) { 262 // Layout may not have been created because it was hidden previously. 263 createLayout(canShow); 264 return; 265 } 266 267 final int newVisibility = canShow && eligibleToShowLayout() ? View.VISIBLE : View.GONE; 268 if (layout.getVisibility() != newVisibility) { 269 layout.setVisibility(newVisibility); 270 } 271 } 272 273 /** Called when display layout changed. */ 274 @VisibleForTesting(visibility = PACKAGE) updateDisplayLayout(DisplayLayout displayLayout)275 public void updateDisplayLayout(DisplayLayout displayLayout) { 276 final Rect prevStableBounds = mStableBounds; 277 final Rect curStableBounds = new Rect(); 278 displayLayout.getStableBounds(curStableBounds); 279 mDisplayLayout = displayLayout; 280 if (!prevStableBounds.equals(curStableBounds)) { 281 // mStableBounds should be updated before we call onParentBoundsChanged. 282 mStableBounds.set(curStableBounds); 283 onParentBoundsChanged(); 284 } 285 } 286 287 /** Called when the surface is ready to be placed under the task surface. */ 288 @VisibleForTesting(visibility = PRIVATE) attachToParentSurface(SurfaceControl.Builder b)289 void attachToParentSurface(SurfaceControl.Builder b) { 290 mTaskListener.attachChildSurfaceToTask(mTaskId, b); 291 } 292 getDisplayId()293 public int getDisplayId() { 294 return mDisplayId; 295 } 296 getTaskId()297 public int getTaskId() { 298 return mTaskId; 299 } 300 301 /** Releases the surface control and tears down the view hierarchy. */ release()302 public void release() { 303 // Hiding before releasing to avoid flickering when transitioning to the Home screen. 304 View layout = getLayout(); 305 if (layout != null) { 306 layout.setVisibility(View.GONE); 307 } 308 removeLayout(); 309 310 if (mViewHost != null) { 311 mViewHost.release(); 312 mViewHost = null; 313 } 314 315 if (mLeash != null) { 316 final SurfaceControl leash = mLeash; 317 mSyncQueue.runInSync(t -> t.remove(leash)); 318 mLeash = null; 319 } 320 } 321 322 /** Re-layouts the view host and updates the surface position. */ relayout()323 void relayout() { 324 relayout(getWindowLayoutParams()); 325 } 326 relayout(WindowManager.LayoutParams windowLayoutParams)327 protected void relayout(WindowManager.LayoutParams windowLayoutParams) { 328 if (mViewHost == null) { 329 return; 330 } 331 mViewHost.relayout(windowLayoutParams); 332 updateSurfacePosition(); 333 } 334 335 @NonNull getLastTaskInfo()336 protected TaskInfo getLastTaskInfo() { 337 return mTaskInfo; 338 } 339 340 /** 341 * Called following a change in the task bounds, display layout stable bounds, or the layout 342 * direction. 343 */ onParentBoundsChanged()344 protected void onParentBoundsChanged() { 345 updateSurfacePosition(); 346 } 347 348 /** 349 * Updates the position of the surface with respect to the parent bounds. 350 */ updateSurfacePosition()351 protected abstract void updateSurfacePosition(); 352 353 /** 354 * Updates the position of the surface with respect to the given {@code positionX} and {@code 355 * positionY}. 356 */ updateSurfacePosition(int positionX, int positionY)357 protected void updateSurfacePosition(int positionX, int positionY) { 358 if (mLeash == null) { 359 return; 360 } 361 mSyncQueue.runInSync(t -> { 362 if (mLeash == null || !mLeash.isValid()) { 363 Log.w(getTag(), "The leash has been released."); 364 return; 365 } 366 t.setPosition(mLeash, positionX, positionY); 367 }); 368 } 369 getLayoutDirection()370 protected int getLayoutDirection() { 371 return mContext.getResources().getConfiguration().getLayoutDirection(); 372 } 373 getTaskBounds()374 protected Rect getTaskBounds() { 375 return mTaskConfig.windowConfiguration.getBounds(); 376 } 377 378 /** Returns the intersection between the task bounds and the display layout stable bounds. */ getTaskStableBounds()379 protected Rect getTaskStableBounds() { 380 final Rect result = new Rect(mStableBounds); 381 result.intersect(getTaskBounds()); 382 return result; 383 } 384 385 /** Creates a {@link SurfaceControlViewHost} for this window manager. */ 386 @VisibleForTesting(visibility = PRIVATE) createSurfaceViewHost()387 public SurfaceControlViewHost createSurfaceViewHost() { 388 return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this, 389 getClass().getSimpleName()); 390 } 391 392 /** Gets the layout params. */ getWindowLayoutParams()393 protected WindowManager.LayoutParams getWindowLayoutParams() { 394 View layout = getLayout(); 395 if (layout == null) { 396 return new WindowManager.LayoutParams(); 397 } 398 // Measure how big the hint is since its size depends on the text size. 399 layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); 400 return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight()); 401 } 402 403 /** Gets the layout params given the width and height of the layout. */ getWindowLayoutParams(int width, int height)404 protected WindowManager.LayoutParams getWindowLayoutParams(int width, int height) { 405 final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams( 406 // Cannot be wrap_content as this determines the actual window size 407 width, height, 408 TYPE_APPLICATION_OVERLAY, 409 getWindowManagerLayoutParamsFlags(), 410 PixelFormat.TRANSLUCENT); 411 winParams.token = new Binder(); 412 winParams.setTitle(getClass().getSimpleName() + mTaskId); 413 winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; 414 return winParams; 415 } 416 417 /** 418 * @return Flags to use for the {@link WindowManager} layout 419 */ getWindowManagerLayoutParamsFlags()420 protected int getWindowManagerLayoutParamsFlags() { 421 return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL; 422 } 423 getTag()424 protected final String getTag() { 425 return getClass().getSimpleName(); 426 } 427 hasTaskListenerChanged(ShellTaskOrganizer.TaskListener newTaskListener)428 protected boolean hasTaskListenerChanged(ShellTaskOrganizer.TaskListener newTaskListener) { 429 return !mTaskListener.equals(newTaskListener); 430 } 431 hasUiModeChanged(TaskInfo currentTaskInfo, TaskInfo newTaskInfo)432 protected static boolean hasUiModeChanged(TaskInfo currentTaskInfo, TaskInfo newTaskInfo) { 433 return currentTaskInfo.configuration.uiMode != newTaskInfo.configuration.uiMode; 434 } 435 } 436