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 attachToParentSurface(IWindow window, SurfaceControl.Builder b)96 protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { 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 b.setParent(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 mDividerView = (DividerView) LayoutInflater.from(mContext) 118 .inflate(R.layout.split_divider, null /* root */); 119 120 final Rect dividerBounds = splitLayout.getDividerBounds(); 121 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 122 dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER, 123 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH 124 | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, 125 PixelFormat.TRANSLUCENT); 126 lp.token = new Binder(); 127 lp.setTitle(mWindowName); 128 lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; 129 mViewHost.setView(mDividerView, lp); 130 mDividerView.setup(splitLayout, this, mViewHost, insetsState); 131 } 132 133 /** 134 * Releases the surface control of the current {@link DividerView} and tear down the view 135 * hierarchy. 136 */ release(@ullable SurfaceControl.Transaction t)137 void release(@Nullable SurfaceControl.Transaction t) { 138 if (mDividerView != null) { 139 mDividerView = null; 140 } 141 142 if (mViewHost != null){ 143 mSyncTransaction = t; 144 mViewHost.release(); 145 mSyncTransaction = null; 146 mViewHost = null; 147 } 148 149 if (mLeash != null) { 150 if (t == null) { 151 new SurfaceControl.Transaction().remove(mLeash).apply(); 152 } else { 153 t.remove(mLeash); 154 } 155 mLeash = null; 156 } 157 } 158 159 @Override removeSurface(SurfaceControl sc)160 protected void removeSurface(SurfaceControl sc) { 161 // This gets called via SurfaceControlViewHost.release() 162 if (mSyncTransaction != null) { 163 mSyncTransaction.remove(sc); 164 } else { 165 super.removeSurface(sc); 166 } 167 } 168 setInteractive(boolean interactive, String from)169 void setInteractive(boolean interactive, String from) { 170 if (mDividerView == null) return; 171 mDividerView.setInteractive(interactive, from); 172 } 173 getDividerView()174 View getDividerView() { 175 return mDividerView; 176 } 177 178 /** 179 * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not 180 * feasible. 181 */ 182 @Nullable getSurfaceControl()183 SurfaceControl getSurfaceControl() { 184 return mLeash; 185 } 186 onInsetsChanged(InsetsState insetsState)187 void onInsetsChanged(InsetsState insetsState) { 188 if (mDividerView != null) { 189 mDividerView.onInsetsChanged(insetsState, true /* animate */); 190 } 191 } 192 } 193