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 com.android.wm.shell.common.split; 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.FLAG_SLIPPERY; 22 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 24 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 25 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; 26 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.graphics.PixelFormat; 30 import android.graphics.Rect; 31 import android.graphics.Region; 32 import android.os.Binder; 33 import android.view.IWindow; 34 import android.view.InsetsState; 35 import android.view.LayoutInflater; 36 import android.view.SurfaceControl; 37 import android.view.SurfaceControlViewHost; 38 import android.view.WindowManager; 39 import android.view.WindowlessWindowManager; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.Nullable; 43 44 import com.android.wm.shell.R; 45 46 /** 47 * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split. 48 */ 49 public final class SplitWindowManager extends WindowlessWindowManager { 50 private static final String TAG = SplitWindowManager.class.getSimpleName(); 51 52 private final String mWindowName; 53 private final ParentContainerCallbacks mParentContainerCallbacks; 54 private Context mContext; 55 private SurfaceControlViewHost mViewHost; 56 private SurfaceControl mLeash; 57 private DividerView mDividerView; 58 59 // Used to "pass" a transaction to WWM.remove so that view removal can be synchronized. 60 private SurfaceControl.Transaction mSyncTransaction = null; 61 62 // For saving/restoring state 63 private boolean mLastDividerInteractive = true; 64 private boolean mLastDividerHandleHidden; 65 66 public interface ParentContainerCallbacks { attachToParentSurface(SurfaceControl.Builder b)67 void attachToParentSurface(SurfaceControl.Builder b); onLeashReady(SurfaceControl leash)68 void onLeashReady(SurfaceControl leash); 69 /** Inflates the given touch zone on the appropriate stage root. */ inflateOnStageRoot(OffscreenTouchZone touchZone)70 void inflateOnStageRoot(OffscreenTouchZone touchZone); 71 } 72 SplitWindowManager(String windowName, Context context, Configuration config, ParentContainerCallbacks parentContainerCallbacks)73 public SplitWindowManager(String windowName, Context context, Configuration config, 74 ParentContainerCallbacks parentContainerCallbacks) { 75 super(config, null /* rootSurface */, null /* hostInputToken */); 76 mContext = context.createConfigurationContext(config); 77 mParentContainerCallbacks = parentContainerCallbacks; 78 mWindowName = windowName; 79 } 80 setTouchRegion(@onNull Rect region)81 void setTouchRegion(@NonNull Rect region) { 82 if (mViewHost != null) { 83 setTouchRegion(mViewHost.getWindowToken().asBinder(), new Region(region)); 84 } 85 } 86 87 @Override getSurfaceControl(IWindow window)88 public SurfaceControl getSurfaceControl(IWindow window) { 89 return super.getSurfaceControl(window); 90 } 91 92 @Override setConfiguration(Configuration configuration)93 public void setConfiguration(Configuration configuration) { 94 super.setConfiguration(configuration); 95 mContext = mContext.createConfigurationContext(configuration); 96 } 97 98 @Override getParentSurface(IWindow window, WindowManager.LayoutParams attrs)99 protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { 100 // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. 101 final SurfaceControl.Builder builder = new SurfaceControl.Builder() 102 .setContainerLayer() 103 .setName(TAG) 104 .setHidden(true) 105 .setCallsite("SplitWindowManager#attachToParentSurface"); 106 mParentContainerCallbacks.attachToParentSurface(builder); 107 mLeash = builder.build(); 108 mParentContainerCallbacks.onLeashReady(mLeash); 109 return mLeash; 110 } 111 112 /** Inflates {@link DividerView} on to the root surface. */ init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring)113 void init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring) { 114 if (mDividerView != null || mViewHost != null) { 115 throw new UnsupportedOperationException( 116 "Try to inflate divider view again without release first"); 117 } 118 119 mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this, 120 "SplitWindowManager"); 121 mDividerView = (DividerView) LayoutInflater.from(mContext) 122 .inflate(R.layout.split_divider, null /* root */); 123 124 final Rect dividerBounds = splitLayout.getDividerBounds(); 125 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 126 dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER, 127 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH 128 | FLAG_SLIPPERY, 129 PixelFormat.TRANSLUCENT); 130 lp.token = new Binder(); 131 lp.setTitle(mWindowName); 132 lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; 133 lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider); 134 mViewHost.setView(mDividerView, lp); 135 mDividerView.setup(splitLayout, this, mViewHost, insetsState); 136 if (isRestoring) { 137 mDividerView.setInteractive(mLastDividerInteractive, mLastDividerHandleHidden, 138 "restore_setup"); 139 } 140 } 141 142 /** 143 * Releases the surface control of the current {@link DividerView} and tear down the view 144 * hierarchy. 145 * @param t If supplied, the surface removal will be bundled with this Transaction. If 146 * called with null, removes the surface immediately. 147 */ release(@ullable SurfaceControl.Transaction t)148 void release(@Nullable SurfaceControl.Transaction t) { 149 if (mDividerView != null) { 150 mLastDividerInteractive = mDividerView.isInteractive(); 151 mLastDividerHandleHidden = mDividerView.isHandleHidden(); 152 mDividerView = null; 153 } 154 155 if (mViewHost != null){ 156 mSyncTransaction = t; 157 mViewHost.release(); 158 mSyncTransaction = null; 159 mViewHost = null; 160 } 161 162 if (mLeash != null) { 163 if (t == null) { 164 new SurfaceControl.Transaction().remove(mLeash).apply(); 165 } else { 166 t.remove(mLeash); 167 } 168 mLeash = null; 169 } 170 } 171 172 @Override removeSurface(SurfaceControl sc)173 protected void removeSurface(SurfaceControl sc) { 174 // This gets called via SurfaceControlViewHost.release() 175 if (mSyncTransaction != null) { 176 mSyncTransaction.remove(sc); 177 } else { 178 super.removeSurface(sc); 179 } 180 } 181 182 /** 183 * Set divider should interactive to user or not. 184 * 185 * @param interactive divider interactive. 186 * @param hideHandle divider handle hidden or not, only work when interactive is false. 187 * @param from caller from where. 188 */ setInteractive(boolean interactive, boolean hideHandle, String from)189 void setInteractive(boolean interactive, boolean hideHandle, String from) { 190 if (mDividerView == null) return; 191 mDividerView.setInteractive(interactive, hideHandle, from); 192 } 193 getDividerView()194 DividerView getDividerView() { 195 return mDividerView; 196 } 197 198 /** 199 * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not 200 * feasible. 201 */ 202 @Nullable getSurfaceControl()203 SurfaceControl getSurfaceControl() { 204 return mLeash; 205 } 206 onInsetsChanged(InsetsState insetsState)207 void onInsetsChanged(InsetsState insetsState) { 208 if (mDividerView != null) { 209 mDividerView.onInsetsChanged(insetsState, true /* animate */); 210 } 211 } 212 } 213