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 public interface ParentContainerCallbacks { attachToParentSurface(SurfaceControl.Builder b)66 void attachToParentSurface(SurfaceControl.Builder b); onLeashReady(SurfaceControl leash)67 void onLeashReady(SurfaceControl leash); 68 } 69 SplitWindowManager(String windowName, Context context, Configuration config, ParentContainerCallbacks parentContainerCallbacks)70 public SplitWindowManager(String windowName, Context context, Configuration config, 71 ParentContainerCallbacks parentContainerCallbacks) { 72 super(config, null /* rootSurface */, null /* hostInputToken */); 73 mContext = context.createConfigurationContext(config); 74 mParentContainerCallbacks = parentContainerCallbacks; 75 mWindowName = windowName; 76 } 77 setTouchRegion(@onNull Rect region)78 void setTouchRegion(@NonNull Rect region) { 79 if (mViewHost != null) { 80 setTouchRegion(mViewHost.getWindowToken().asBinder(), new Region(region)); 81 } 82 } 83 84 @Override getSurfaceControl(IWindow window)85 public SurfaceControl getSurfaceControl(IWindow window) { 86 return super.getSurfaceControl(window); 87 } 88 89 @Override setConfiguration(Configuration configuration)90 public void setConfiguration(Configuration configuration) { 91 super.setConfiguration(configuration); 92 mContext = mContext.createConfigurationContext(configuration); 93 } 94 95 @Override getParentSurface(IWindow window, WindowManager.LayoutParams attrs)96 protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) { 97 // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later. 98 final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) 99 .setContainerLayer() 100 .setName(TAG) 101 .setHidden(true) 102 .setCallsite("SplitWindowManager#attachToParentSurface"); 103 mParentContainerCallbacks.attachToParentSurface(builder); 104 mLeash = builder.build(); 105 mParentContainerCallbacks.onLeashReady(mLeash); 106 return mLeash; 107 } 108 109 /** Inflates {@link DividerView} on to the root surface. */ init(SplitLayout splitLayout, InsetsState insetsState)110 void init(SplitLayout splitLayout, InsetsState insetsState) { 111 if (mDividerView != null || mViewHost != null) { 112 throw new UnsupportedOperationException( 113 "Try to inflate divider view again without release first"); 114 } 115 116 mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this, 117 "SplitWindowManager"); 118 mDividerView = (DividerView) LayoutInflater.from(mContext) 119 .inflate(R.layout.split_divider, null /* root */); 120 121 final Rect dividerBounds = splitLayout.getDividerBounds(); 122 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 123 dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER, 124 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH 125 | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, 126 PixelFormat.TRANSLUCENT); 127 lp.token = new Binder(); 128 lp.setTitle(mWindowName); 129 lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; 130 lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider); 131 mViewHost.setView(mDividerView, lp); 132 mDividerView.setup(splitLayout, this, mViewHost, insetsState); 133 } 134 135 /** 136 * Releases the surface control of the current {@link DividerView} and tear down the view 137 * hierarchy. 138 */ release(@ullable SurfaceControl.Transaction t)139 void release(@Nullable SurfaceControl.Transaction t) { 140 if (mDividerView != null) { 141 mDividerView = null; 142 } 143 144 if (mViewHost != null){ 145 mSyncTransaction = t; 146 mViewHost.release(); 147 mSyncTransaction = null; 148 mViewHost = null; 149 } 150 151 if (mLeash != null) { 152 if (t == null) { 153 new SurfaceControl.Transaction().remove(mLeash).apply(); 154 } else { 155 t.remove(mLeash); 156 } 157 mLeash = null; 158 } 159 } 160 161 @Override removeSurface(SurfaceControl sc)162 protected void removeSurface(SurfaceControl sc) { 163 // This gets called via SurfaceControlViewHost.release() 164 if (mSyncTransaction != null) { 165 mSyncTransaction.remove(sc); 166 } else { 167 super.removeSurface(sc); 168 } 169 } 170 171 /** 172 * Set divider should interactive to user or not. 173 * 174 * @param interactive divider interactive. 175 * @param hideHandle divider handle hidden or not, only work when interactive is false. 176 * @param from caller from where. 177 */ setInteractive(boolean interactive, boolean hideHandle, String from)178 void setInteractive(boolean interactive, boolean hideHandle, String from) { 179 if (mDividerView == null) return; 180 mDividerView.setInteractive(interactive, hideHandle, from); 181 } 182 getDividerView()183 View getDividerView() { 184 return mDividerView; 185 } 186 187 /** 188 * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not 189 * feasible. 190 */ 191 @Nullable getSurfaceControl()192 SurfaceControl getSurfaceControl() { 193 return mLeash; 194 } 195 onInsetsChanged(InsetsState insetsState)196 void onInsetsChanged(InsetsState insetsState) { 197 if (mDividerView != null) { 198 mDividerView.onInsetsChanged(insetsState, true /* animate */); 199 } 200 } 201 } 202