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