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