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