1 /*
2  * Copyright (C) 2019 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.server.policy;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.hardware.devicestate.DeviceStateManager;
22 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
23 import android.hardware.display.DisplayManagerInternal;
24 import android.os.Handler;
25 import android.os.HandlerExecutor;
26 import android.os.RemoteCallbackList;
27 import android.os.RemoteException;
28 import android.view.DisplayInfo;
29 import android.view.IDisplayFoldListener;
30 
31 import com.android.server.DisplayThread;
32 import com.android.server.LocalServices;
33 import com.android.server.wm.WindowManagerInternal;
34 
35 /**
36  * Controls the behavior of foldable devices whose screen can literally bend and fold.
37  * TODO(b/126160895): Move DisplayFoldController from PhoneWindowManager to DisplayPolicy.
38  */
39 class DisplayFoldController {
40     private final WindowManagerInternal mWindowManagerInternal;
41     private final DisplayManagerInternal mDisplayManagerInternal;
42     private final int mDisplayId;
43     private final Handler mHandler;
44 
45     /** The display area while device is folded. */
46     private final Rect mFoldedArea;
47     /** The display area to override the original folded area. */
48     private Rect mOverrideFoldedArea = new Rect();
49 
50     private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
51     private final RemoteCallbackList<IDisplayFoldListener> mListeners = new RemoteCallbackList<>();
52     private Boolean mFolded;
53     private String mFocusedApp;
54     private final DisplayFoldDurationLogger mDurationLogger = new DisplayFoldDurationLogger();
55 
DisplayFoldController( Context context, WindowManagerInternal windowManagerInternal, DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea, Handler handler)56     DisplayFoldController(
57             Context context, WindowManagerInternal windowManagerInternal,
58             DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea,
59             Handler handler) {
60         mWindowManagerInternal = windowManagerInternal;
61         mDisplayManagerInternal = displayManagerInternal;
62         mDisplayId = displayId;
63         mFoldedArea = new Rect(foldedArea);
64         mHandler = handler;
65 
66         DeviceStateManager deviceStateManager = context.getSystemService(DeviceStateManager.class);
67         deviceStateManager.registerCallback(new HandlerExecutor(handler),
68                 new FoldStateListener(context, folded -> setDeviceFolded(folded)));
69     }
70 
finishedGoingToSleep()71     void finishedGoingToSleep() {
72         mDurationLogger.onFinishedGoingToSleep();
73     }
74 
finishedWakingUp()75     void finishedWakingUp() {
76         mDurationLogger.onFinishedWakingUp(mFolded);
77     }
78 
setDeviceFolded(boolean folded)79     private void setDeviceFolded(boolean folded) {
80         if (mFolded != null && mFolded == folded) {
81             return;
82         }
83 
84         final Rect foldedArea;
85         if (!mOverrideFoldedArea.isEmpty()) {
86             foldedArea = mOverrideFoldedArea;
87         } else if (!mFoldedArea.isEmpty()) {
88             foldedArea = mFoldedArea;
89         } else {
90             foldedArea = null;
91         }
92 
93         // Only do display scaling/cropping if it has been configured to do so
94         if (foldedArea != null) {
95             if (folded) {
96 
97                 mDisplayManagerInternal.getNonOverrideDisplayInfo(
98                         mDisplayId, mNonOverrideDisplayInfo);
99                 final int dx = (mNonOverrideDisplayInfo.logicalWidth - foldedArea.width()) / 2
100                         - foldedArea.left;
101                 final int dy = (mNonOverrideDisplayInfo.logicalHeight - foldedArea.height()) / 2
102                         - foldedArea.top;
103 
104                 // Bypass scaling otherwise LogicalDisplay will scale contents by default.
105                 mDisplayManagerInternal.setDisplayScalingDisabled(mDisplayId, true);
106                 mWindowManagerInternal.setForcedDisplaySize(mDisplayId,
107                         foldedArea.width(), foldedArea.height());
108                 mDisplayManagerInternal.setDisplayOffsets(mDisplayId, -dx, -dy);
109             } else {
110                 mDisplayManagerInternal.setDisplayScalingDisabled(mDisplayId, false);
111                 mWindowManagerInternal.clearForcedDisplaySize(mDisplayId);
112                 mDisplayManagerInternal.setDisplayOffsets(mDisplayId, 0, 0);
113             }
114         }
115 
116         mDurationLogger.setDeviceFolded(folded);
117         mDurationLogger.logFocusedAppWithFoldState(folded, mFocusedApp);
118         mFolded = folded;
119 
120         final int n = mListeners.beginBroadcast();
121         for (int i = 0; i < n; i++) {
122             try {
123                 mListeners.getBroadcastItem(i).onDisplayFoldChanged(mDisplayId, folded);
124             } catch (RemoteException e) {
125                 // Listener died.
126             }
127         }
128         mListeners.finishBroadcast();
129     }
130 
registerDisplayFoldListener(IDisplayFoldListener listener)131     void registerDisplayFoldListener(IDisplayFoldListener listener) {
132         mListeners.register(listener);
133         if (mFolded == null) {
134             return;
135         }
136         mHandler.post(() -> {
137             try {
138                 listener.onDisplayFoldChanged(mDisplayId, mFolded);
139             } catch (RemoteException e) {
140                 // Listener died.
141             }
142         });
143     }
144 
unregisterDisplayFoldListener(IDisplayFoldListener listener)145     void unregisterDisplayFoldListener(IDisplayFoldListener listener) {
146         mListeners.unregister(listener);
147     }
148 
setOverrideFoldedArea(Rect area)149     void setOverrideFoldedArea(Rect area) {
150         mOverrideFoldedArea.set(area);
151     }
152 
getFoldedArea()153     Rect getFoldedArea() {
154         if (!mOverrideFoldedArea.isEmpty()) {
155             return mOverrideFoldedArea;
156         } else {
157             return mFoldedArea;
158         }
159     }
160 
onDefaultDisplayFocusChanged(String pkg)161     void onDefaultDisplayFocusChanged(String pkg) {
162         mFocusedApp = pkg;
163     }
164 
create(Context context, int displayId)165     static DisplayFoldController create(Context context, int displayId) {
166         final WindowManagerInternal windowManagerService =
167                 LocalServices.getService(WindowManagerInternal.class);
168         final DisplayManagerInternal displayService =
169                 LocalServices.getService(DisplayManagerInternal.class);
170 
171         final String configFoldedArea = context.getResources().getString(
172                 com.android.internal.R.string.config_foldedArea);
173         final Rect foldedArea;
174         if (configFoldedArea == null || configFoldedArea.isEmpty()) {
175             foldedArea = new Rect();
176         } else {
177             foldedArea = Rect.unflattenFromString(configFoldedArea);
178         }
179 
180         return new DisplayFoldController(context, windowManagerService, displayService,
181                 displayId, foldedArea, DisplayThread.getHandler());
182     }
183 }
184