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