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