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