• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 package com.android.settings.connecteddevice.display;
17 
18 import static android.content.Context.DISPLAY_SERVICE;
19 import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
20 import static android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_ADDED;
21 import static android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_CHANGED;
22 import static android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_REMOVED;
23 import static android.hardware.display.DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED;
24 import static android.view.Display.INVALID_DISPLAY;
25 
26 import static com.android.server.display.feature.flags.Flags.enableModeLimitForExternalDisplay;
27 
28 import android.content.Context;
29 import android.hardware.display.DisplayManager;
30 import android.hardware.display.DisplayManagerGlobal;
31 import android.os.Handler;
32 import android.os.Looper;
33 import android.os.RemoteException;
34 import android.os.SystemProperties;
35 import android.view.Display;
36 import android.view.Display.Mode;
37 import android.view.IWindowManager;
38 import android.view.WindowManagerGlobal;
39 
40 import androidx.annotation.NonNull;
41 import androidx.annotation.Nullable;
42 
43 import com.android.settings.R;
44 import com.android.settings.flags.FeatureFlags;
45 import com.android.settings.flags.FeatureFlagsImpl;
46 
47 import java.util.ArrayList;
48 import java.util.HashSet;
49 import java.util.List;
50 
51 public class ExternalDisplaySettingsConfiguration {
52     static final String VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY =
53             "persist.demo.userrotation.package_name";
54     static final String DISPLAY_ID_ARG = "display_id";
55     static final int EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE = R.string.external_display_not_found;
56     static final int EXTERNAL_DISPLAY_HELP_URL = R.string.help_url_external_display;
57 
58     public static class SystemServicesProvider {
59         @Nullable
60         private IWindowManager mWindowManager;
61         @Nullable
62         private DisplayManager mDisplayManager;
63         @Nullable
64         protected Context mContext;
65         /**
66          * @param name of a system property.
67          * @return the value of the system property.
68          */
69         @NonNull
getSystemProperty(@onNull String name)70         public String getSystemProperty(@NonNull String name) {
71             return SystemProperties.get(name);
72         }
73 
74         /**
75          * @return return public Display manager.
76          */
77         @Nullable
getDisplayManager()78         public DisplayManager getDisplayManager() {
79             if (mDisplayManager == null && getContext() != null) {
80                 mDisplayManager = (DisplayManager) getContext().getSystemService(DISPLAY_SERVICE);
81             }
82             return mDisplayManager;
83         }
84 
85         /**
86          * @return internal IWindowManager
87          */
88         @Nullable
getWindowManager()89         public IWindowManager getWindowManager() {
90             if (mWindowManager == null) {
91                 mWindowManager = WindowManagerGlobal.getWindowManagerService();
92             }
93             return mWindowManager;
94         }
95 
96         /**
97          * @return context.
98          */
99         @Nullable
getContext()100         public Context getContext() {
101             return mContext;
102         }
103     }
104 
105     public static class Injector extends SystemServicesProvider {
106         @NonNull
107         private final FeatureFlags mFlags;
108         @NonNull
109         private final Handler mHandler;
110 
Injector(@ullable Context context)111         Injector(@Nullable Context context) {
112             this(context, new DesktopExperienceFlags(new FeatureFlagsImpl()),
113                     new Handler(Looper.getMainLooper()));
114         }
115 
Injector(@ullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler)116         Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
117             mContext = context;
118             mFlags = flags;
119             mHandler = handler;
120         }
121 
wrapDmDisplay(Display display, DisplayIsEnabled isEnabled)122         private static DisplayDevice wrapDmDisplay(Display display, DisplayIsEnabled isEnabled) {
123             return new DisplayDevice(display.getDisplayId(), display.getName(),
124                         display.getMode(), List.<Mode>of(display.getSupportedModes()), isEnabled);
125         }
126 
127         /**
128          * @return all displays including disabled.
129          */
130         @NonNull
getConnectedDisplays()131         public List<DisplayDevice> getConnectedDisplays() {
132             var dm = getDisplayManager();
133             if (dm == null) {
134                 return List.of();
135             }
136 
137             var enabledIds = new HashSet<Integer>();
138             for (Display d : dm.getDisplays()) {
139                 enabledIds.add(d.getDisplayId());
140             }
141 
142             var displays = new ArrayList<DisplayDevice>();
143             for (Display d : dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
144                 if (!isDisplayAllowed(d, this)) {
145                     continue;
146                 }
147                 var isEnabled = enabledIds.contains(d.getDisplayId())
148                         ? DisplayIsEnabled.YES : DisplayIsEnabled.NO;
149                 displays.add(wrapDmDisplay(d, isEnabled));
150             }
151             return displays;
152         }
153 
154         /**
155          * @param displayId which must be returned
156          * @return display object for the displayId, or null if display is not a connected display,
157          *         the ID was not found, or the ID was invalid
158          */
159         @Nullable
getDisplay(int displayId)160         public DisplayDevice getDisplay(int displayId) {
161             if (displayId == INVALID_DISPLAY) {
162                 return null;
163             }
164             var dm = getDisplayManager();
165             if (dm == null) {
166                 return null;
167             }
168             var display = dm.getDisplay(displayId);
169             if (display == null || !isDisplayAllowed(display, this)) {
170                 return null;
171             }
172             return wrapDmDisplay(display, DisplayIsEnabled.UNKNOWN);
173         }
174 
175         /**
176          * Register display listener.
177          */
registerDisplayListener(@onNull DisplayManager.DisplayListener listener)178         public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
179             var dm = getDisplayManager();
180             if (dm == null) {
181                 return;
182             }
183             dm.registerDisplayListener(listener, mHandler, EVENT_TYPE_DISPLAY_ADDED
184                     | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED,
185                     PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED);
186         }
187 
188         /**
189          * Unregister display listener.
190          */
unregisterDisplayListener(@onNull DisplayManager.DisplayListener listener)191         public void unregisterDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
192             var dm = getDisplayManager();
193             if (dm == null) {
194                 return;
195             }
196             dm.unregisterDisplayListener(listener);
197         }
198 
199         /**
200          * @return feature flags.
201          */
202         @NonNull
getFlags()203         public FeatureFlags getFlags() {
204             return mFlags;
205         }
206 
207         /**
208          * Enable connected display.
209          */
enableConnectedDisplay(int displayId)210         public boolean enableConnectedDisplay(int displayId) {
211             var dm = getDisplayManager();
212             if (dm == null) {
213                 return false;
214             }
215             dm.enableConnectedDisplay(displayId);
216             return true;
217         }
218 
219         /**
220          * Disable connected display.
221          */
disableConnectedDisplay(int displayId)222         public boolean disableConnectedDisplay(int displayId) {
223             var dm = getDisplayManager();
224             if (dm == null) {
225                 return false;
226             }
227             dm.disableConnectedDisplay(displayId);
228             return true;
229         }
230 
231         /**
232          * @return handler
233          */
234         @NonNull
getHandler()235         public Handler getHandler() {
236             return mHandler;
237         }
238 
239         /**
240          * Get display rotation
241          * @param displayId display identifier
242          * @return rotation
243          */
getDisplayUserRotation(int displayId)244         public int getDisplayUserRotation(int displayId) {
245             var wm = getWindowManager();
246             if (wm == null) {
247                 return 0;
248             }
249             try {
250                 return wm.getDisplayUserRotation(displayId);
251             } catch (RemoteException e) {
252                 return 0;
253             }
254         }
255 
256         /**
257          * Freeze rotation of the display in the specified rotation.
258          * @param displayId display identifier
259          * @param rotation [0, 1, 2, 3]
260          * @return true if successful
261          */
freezeDisplayRotation(int displayId, int rotation)262         public boolean freezeDisplayRotation(int displayId, int rotation) {
263             var wm = getWindowManager();
264             if (wm == null) {
265                 return false;
266             }
267             try {
268                 wm.freezeDisplayRotation(displayId, rotation,
269                         "ExternalDisplayPreferenceFragment");
270                 return true;
271             } catch (RemoteException e) {
272                 return false;
273             }
274         }
275 
276         /**
277          * Enforce display mode on the given display.
278          */
setUserPreferredDisplayMode(int displayId, @NonNull Mode mode)279         public void setUserPreferredDisplayMode(int displayId, @NonNull Mode mode) {
280             DisplayManagerGlobal.getInstance().setUserPreferredDisplayMode(displayId, mode);
281         }
282 
283         /**
284          * @return true if the display mode limit flag enabled.
285          */
isModeLimitForExternalDisplayEnabled()286         public boolean isModeLimitForExternalDisplayEnabled() {
287             return enableModeLimitForExternalDisplay();
288         }
289     }
290 
291     public abstract static class DisplayListener implements DisplayManager.DisplayListener {
292         @Override
onDisplayAdded(int displayId)293         public void onDisplayAdded(int displayId) {
294             update(displayId);
295         }
296 
297         @Override
onDisplayRemoved(int displayId)298         public void onDisplayRemoved(int displayId) {
299             update(displayId);
300         }
301 
302         @Override
onDisplayChanged(int displayId)303         public void onDisplayChanged(int displayId) {
304             update(displayId);
305         }
306 
307         @Override
onDisplayConnected(int displayId)308         public void onDisplayConnected(int displayId) {
309             update(displayId);
310         }
311 
312         @Override
onDisplayDisconnected(int displayId)313         public void onDisplayDisconnected(int displayId) {
314             update(displayId);
315         }
316 
317         /**
318          * Called from other listener methods to trigger update of the settings page.
319          */
update(int displayId)320         public abstract void update(int displayId);
321     }
322 
323     /**
324      * @return whether the settings page is enabled or not.
325      */
isExternalDisplaySettingsPageEnabled(@onNull FeatureFlags flags)326     public static boolean isExternalDisplaySettingsPageEnabled(@NonNull FeatureFlags flags) {
327         return flags.rotationConnectedDisplaySetting()
328                 || flags.resolutionAndEnableConnectedDisplaySetting()
329                 || flags.displayTopologyPaneInDisplayList();
330     }
331 
isDisplayAllowed(Display display, SystemServicesProvider props)332     private static boolean isDisplayAllowed(Display display, SystemServicesProvider props) {
333         return display.getType() == Display.TYPE_EXTERNAL
334                 || display.getType() == Display.TYPE_OVERLAY
335                 || isVirtualDisplayAllowed(display, props);
336     }
337 
isTopologyPaneEnabled(@ullable Injector injector)338     static boolean isTopologyPaneEnabled(@Nullable Injector injector) {
339         return injector != null && injector.getFlags().displayTopologyPaneInDisplayList();
340     }
341 
isVirtualDisplayAllowed(@onNull Display display, @NonNull SystemServicesProvider properties)342     private static boolean isVirtualDisplayAllowed(@NonNull Display display,
343             @NonNull SystemServicesProvider properties) {
344         var sysProp = properties.getSystemProperty(VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY);
345         return !sysProp.isEmpty() && display.getType() == Display.TYPE_VIRTUAL
346                        && sysProp.equals(display.getOwnerPackageName());
347     }
348 
isUseDisplaySettingEnabled(@ullable Injector injector)349     static boolean isUseDisplaySettingEnabled(@Nullable Injector injector) {
350         return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting()
351                 && !injector.getFlags().displayTopologyPaneInDisplayList();
352     }
353 
isResolutionSettingEnabled(@ullable Injector injector)354     static boolean isResolutionSettingEnabled(@Nullable Injector injector) {
355         return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
356     }
357 
isRotationSettingEnabled(@ullable Injector injector)358     static boolean isRotationSettingEnabled(@Nullable Injector injector) {
359         return injector != null && injector.getFlags().rotationConnectedDisplaySetting();
360     }
361 
isDisplaySizeSettingEnabled(@ullable Injector injector)362     static boolean isDisplaySizeSettingEnabled(@Nullable Injector injector) {
363         return injector != null && injector.getFlags().displaySizeConnectedDisplaySetting();
364     }
365 }
366