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.WindowManager;
41 import android.view.WindowlessWindowManager;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 
46 import com.android.wm.shell.R;
47 
48 /**
49  * Holds view hierarchy of a root surface and helps to inflate {@link DividerView} for a split.
50  */
51 public final class SplitWindowManager extends WindowlessWindowManager {
52     private static final String TAG = SplitWindowManager.class.getSimpleName();
53 
54     private final String mWindowName;
55     private final ParentContainerCallbacks mParentContainerCallbacks;
56     private Context mContext;
57     private SurfaceControlViewHost mViewHost;
58     private SurfaceControl mLeash;
59     private DividerView mDividerView;
60 
61     public interface ParentContainerCallbacks {
attachToParentSurface(SurfaceControl.Builder b)62         void attachToParentSurface(SurfaceControl.Builder b);
onLeashReady(SurfaceControl leash)63         void onLeashReady(SurfaceControl leash);
64     }
65 
SplitWindowManager(String windowName, Context context, Configuration config, ParentContainerCallbacks parentContainerCallbacks)66     public SplitWindowManager(String windowName, Context context, Configuration config,
67             ParentContainerCallbacks parentContainerCallbacks) {
68         super(config, null /* rootSurface */, null /* hostInputToken */);
69         mContext = context.createConfigurationContext(config);
70         mParentContainerCallbacks = parentContainerCallbacks;
71         mWindowName = windowName;
72     }
73 
setTouchRegion(@onNull Rect region)74     void setTouchRegion(@NonNull Rect region) {
75         if (mViewHost != null) {
76             setTouchRegion(mViewHost.getWindowToken().asBinder(), new Region(region));
77         }
78     }
79 
80     @Override
getSurfaceControl(IWindow window)81     public SurfaceControl getSurfaceControl(IWindow window) {
82         return super.getSurfaceControl(window);
83     }
84 
85     @Override
setConfiguration(Configuration configuration)86     public void setConfiguration(Configuration configuration) {
87         super.setConfiguration(configuration);
88         mContext = mContext.createConfigurationContext(configuration);
89     }
90 
91     @Override
attachToParentSurface(IWindow window, SurfaceControl.Builder b)92     protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
93         // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
94         final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
95                 .setContainerLayer()
96                 .setName(TAG)
97                 .setHidden(true)
98                 .setCallsite("SplitWindowManager#attachToParentSurface");
99         mParentContainerCallbacks.attachToParentSurface(builder);
100         mLeash = builder.build();
101         mParentContainerCallbacks.onLeashReady(mLeash);
102         b.setParent(mLeash);
103     }
104 
105     /** Inflates {@link DividerView} on to the root surface. */
init(SplitLayout splitLayout, InsetsState insetsState)106     void init(SplitLayout splitLayout, InsetsState insetsState) {
107         if (mDividerView != null || mViewHost != null) {
108             throw new UnsupportedOperationException(
109                     "Try to inflate divider view again without release first");
110         }
111 
112         mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
113         mDividerView = (DividerView) LayoutInflater.from(mContext)
114                 .inflate(R.layout.split_divider, null /* root */);
115 
116         final Rect dividerBounds = splitLayout.getDividerBounds();
117         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
118                 dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
119                 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
120                         | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
121                 PixelFormat.TRANSLUCENT);
122         lp.token = new Binder();
123         lp.setTitle(mWindowName);
124         lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
125         mViewHost.setView(mDividerView, lp);
126         mDividerView.setup(splitLayout, this, mViewHost, insetsState);
127     }
128 
129     /**
130      * Releases the surface control of the current {@link DividerView} and tear down the view
131      * hierarchy.
132      */
release()133     void release() {
134         if (mDividerView != null) {
135             mDividerView = null;
136         }
137 
138         if (mViewHost != null){
139             mViewHost.release();
140             mViewHost = null;
141         }
142 
143         if (mLeash != null) {
144             new SurfaceControl.Transaction().remove(mLeash).apply();
145             mLeash = null;
146         }
147     }
148 
setInteractive(boolean interactive)149     void setInteractive(boolean interactive) {
150         if (mDividerView == null) return;
151         mDividerView.setInteractive(interactive);
152     }
153 
154     /**
155      * Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
156      * feasible.
157      */
158     @Nullable
getSurfaceControl()159     SurfaceControl getSurfaceControl() {
160         return mLeash;
161     }
162 
onInsetsChanged(InsetsState insetsState)163     void onInsetsChanged(InsetsState insetsState) {
164         if (mDividerView != null) {
165             mDividerView.onInsetsChanged(insetsState, true /* animate */);
166         }
167     }
168 }
169