Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e1e4f18 authored by Matthew DeVore's avatar Matthew DeVore Committed by Android (Google) Code Review
Browse files

Merge changes I95721d9b,Id43d2f10 into main

* changes:
  Connected displays: use Robolectric for tests
  CD settings: minimal DisplayDevice metadata class
parents b0103842 0a583b22
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.connecteddevice.display

import android.view.Display.Mode

import androidx.annotation.Keep

enum class DisplayIsEnabled { YES, NO, UNKNOWN }

/**
 * Contains essential information from {@link android.view.Display} needed by the user to configure
 * a display.
 */
@Keep
data class DisplayDevice(val id: Int, val name: String, val mode: Mode?,
        val supportedModes: List<Mode>, val isEnabled: DisplayIsEnabled) {}
+27 −41
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.settings.connecteddevice.display;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplaySizeSettingEnabled;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isResolutionSettingEnabled;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isRotationSettingEnabled;
@@ -32,7 +31,6 @@ import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.Choreographer;
import android.view.Display;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -59,7 +57,6 @@ import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.IllustrationPreference;
import com.android.settingslib.widget.MainSwitchPreference;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@@ -398,7 +395,8 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
    }

    private void updateScreen(final PrefRefresh screen, Context context) {
        final var displaysToShow = externalDisplaysToShow();
        final var displaysToShow = mInjector == null
                ? List.<DisplayDevice>of() : mInjector.getConnectedDisplays();

        if (displaysToShow.isEmpty()) {
            showTextWhenNoDisplaysToShow(screen, context, /* position= */ 0);
@@ -438,19 +436,18 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
        return category;
    }

    private void showDisplaySettings(Display display, PrefRefresh refresh,
    private void showDisplaySettings(DisplayDevice display, PrefRefresh refresh,
            Context context, boolean includeV1Helpers, int position) {
        final var isEnabled = mInjector != null && mInjector.isDisplayEnabled(display);
        if (isUseDisplaySettingEnabled(mInjector)) {
            addUseDisplayPreferenceForDisplay(context, refresh, display, isEnabled, position);
            addUseDisplayPreferenceForDisplay(context, refresh, display, position);
        }
        final var displayRotation = getDisplayRotation(display.getDisplayId());
        if (includeV1Helpers && isEnabled) {
        final var displayRotation = getDisplayRotation(display.getId());
        if (includeV1Helpers && display.isEnabled() == DisplayIsEnabled.YES) {
            addIllustrationImage(context, refresh, displayRotation);
        }

        addResolutionPreference(context, refresh, display, position, isEnabled);
        addRotationPreference(context, refresh, display, displayRotation, position, isEnabled);
        addResolutionPreference(context, refresh, display, position);
        addRotationPreference(context, refresh, display, displayRotation, position);
        if (isResolutionSettingEnabled(mInjector)) {
            // Do not show the footer about changing resolution affecting apps. This is not in the
            // UX design for v2, and there is no good place to put it, since (a) if it is on the
@@ -462,13 +459,13 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
            // inconsistent with the topology pane, which shows that display.
            // TODO(b/352648432): probably remove footer once the pane and rest of v2 UI is in
            // place.
            if (includeV1Helpers && isEnabled) {
            if (includeV1Helpers && display.isEnabled() == DisplayIsEnabled.YES) {
                addFooterPreference(
                        context, refresh, EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE);
            }
        }
        if (isDisplaySizeSettingEnabled(mInjector)) {
            addSizePreference(context, refresh, display.getDisplayId(), position, isEnabled);
            addSizePreference(context, refresh, display, position);
        }
    }

@@ -485,7 +482,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
        }
    }

    private void showDisplaysList(@NonNull List<Display> displaysToShow,
    private void showDisplaysList(@NonNull List<DisplayDevice> displaysToShow,
            @NonNull PrefRefresh screen, @NonNull Context context) {
        maybeAddV2Components(context, screen);
        int position = 0;
@@ -504,19 +501,6 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
        }
    }

    private List<Display> externalDisplaysToShow() {
        if (mInjector == null) {
            return List.of();
        }
        var displaysToShow = new ArrayList<Display>();
        for (var display : mInjector.getAllDisplays()) {
            if (display != null && isDisplayAllowed(display, mInjector)) {
                displaysToShow.add(display);
            }
        }
        return displaysToShow;
    }

    private void addUseDisplayPreferenceNoDisplaysFound(Context context, PrefRefresh refresh,
            int position) {
        final var pref = reuseUseDisplayPreference(context, refresh, position);
@@ -526,9 +510,9 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
    }

    private void addUseDisplayPreferenceForDisplay(final Context context,
            PrefRefresh refresh, final Display display, boolean isEnabled, int position) {
            PrefRefresh refresh, final DisplayDevice display, int position) {
        final var pref = reuseUseDisplayPreference(context, refresh, position);
        pref.setChecked(isEnabled);
        pref.setChecked(display.isEnabled() == DisplayIsEnabled.YES);
        pref.setEnabled(true);
        pref.setOnPreferenceChangeListener((p, newValue) -> {
            writePreferenceClickMetric(p);
@@ -537,9 +521,9 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
                return false;
            }
            if ((Boolean) newValue) {
                result = mInjector.enableConnectedDisplay(display.getDisplayId());
                result = mInjector.enableConnectedDisplay(display.getId());
            } else {
                result = mInjector.disableConnectedDisplay(display.getDisplayId());
                result = mInjector.disableConnectedDisplay(display.getId());
            }
            if (result) {
                pref.setChecked((Boolean) newValue);
@@ -559,7 +543,7 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
    }

    private void addRotationPreference(final Context context, PrefRefresh refresh,
            final Display display, final int displayRotation, int position, boolean isEnabled) {
            final DisplayDevice display, final int displayRotation, int position) {
        var pref = reuseRotationPreference(context, refresh, position);
        if (mRotationEntries == null || mRotationEntriesValues == null) {
            mRotationEntries = new String[] {
@@ -576,39 +560,41 @@ public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmen
        pref.setOnPreferenceChangeListener((p, newValue) -> {
            writePreferenceClickMetric(p);
            var rotation = Integer.parseInt((String) newValue);
            var displayId = display.getDisplayId();
            var displayId = display.getId();
            if (mInjector == null || !mInjector.freezeDisplayRotation(displayId, rotation)) {
                return false;
            }
            pref.setValueIndex(rotation);
            return true;
        });
        pref.setEnabled(isEnabled && isRotationSettingEnabled(mInjector));
        pref.setEnabled(display.isEnabled() == DisplayIsEnabled.YES
                && isRotationSettingEnabled(mInjector));
    }

    private void addResolutionPreference(final Context context, PrefRefresh refresh,
            final Display display, int position, boolean isEnabled) {
            final DisplayDevice display, int position) {
        var pref = reuseResolutionPreference(context, refresh, position);
        pref.setSummary(display.getMode().getPhysicalWidth() + " x "
                + display.getMode().getPhysicalHeight());
        pref.setOnPreferenceClickListener((Preference p) -> {
            writePreferenceClickMetric(p);
            launchResolutionSelector(context, display.getDisplayId());
            launchResolutionSelector(context, display.getId());
            return true;
        });
        pref.setEnabled(isEnabled && isResolutionSettingEnabled(mInjector));
        pref.setEnabled(display.isEnabled() == DisplayIsEnabled.YES
                && isResolutionSettingEnabled(mInjector));
    }

    private void addSizePreference(final Context context, PrefRefresh refresh, int displayId,
            int position, boolean isEnabled) {
        var pref = reuseSizePreference(context, refresh, displayId, position);
    private void addSizePreference(final Context context, PrefRefresh refresh,
            DisplayDevice display, int position) {
        var pref = reuseSizePreference(context, refresh, display.getId(), position);
        pref.setSummary(EXTERNAL_DISPLAY_SIZE_SUMMARY_RESOURCE);
        pref.setOnPreferenceClickListener(
                (Preference p) -> {
                    writePreferenceClickMetric(p);
                    return true;
                });
        pref.setEnabled(isEnabled);
        pref.setEnabled(display.isEnabled() == DisplayIsEnabled.YES);
    }

    private int getDisplayRotation(int displayId) {
+42 −38
Original line number Diff line number Diff line
@@ -44,6 +44,10 @@ import com.android.settings.R;
import com.android.settings.flags.FeatureFlags;
import com.android.settings.flags.FeatureFlagsImpl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

public class ExternalDisplaySettingsConfiguration {
    static final String VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY =
            "persist.demo.userrotation.package_name";
@@ -114,40 +118,57 @@ public class ExternalDisplaySettingsConfiguration {
            mHandler = handler;
        }

        private static DisplayDevice wrapDmDisplay(Display display, DisplayIsEnabled isEnabled) {
            return new DisplayDevice(display.getDisplayId(), display.getName(),
                        display.getMode(), List.<Mode>of(display.getSupportedModes()), isEnabled);
        }

        /**
         * @return all displays including disabled.
         */
        @NonNull
        public Display[] getAllDisplays() {
        public List<DisplayDevice> getConnectedDisplays() {
            var dm = getDisplayManager();
            if (dm == null) {
                return new Display[0];
                return List.of();
            }
            return dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);

            var enabledIds = new HashSet<Integer>();
            for (Display d : dm.getDisplays()) {
                enabledIds.add(d.getDisplayId());
            }

        /**
         * @return enabled displays only.
         */
        @NonNull
        public Display[] getEnabledDisplays() {
            var dm = getDisplayManager();
            if (dm == null) {
                return new Display[0];
            var displays = new ArrayList<DisplayDevice>();
            for (Display d : dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
                if (!isDisplayAllowed(d, this)) {
                    continue;
                }
            return dm.getDisplays();
                var isEnabled = enabledIds.contains(d.getDisplayId())
                        ? DisplayIsEnabled.YES : DisplayIsEnabled.NO;
                displays.add(wrapDmDisplay(d, isEnabled));
            }
            return displays;
        }

        /**
         * @return true if the display is enabled
         * @param displayId which must be returned
         * @return display object for the displayId, or null if display is not a connected display,
         *         the ID was not found, or the ID was invalid
         */
        public boolean isDisplayEnabled(@NonNull Display display) {
            for (var enabledDisplay : getEnabledDisplays()) {
                if (enabledDisplay.getDisplayId() == display.getDisplayId()) {
                    return true;
        @Nullable
        public DisplayDevice getDisplay(int displayId) {
            if (displayId == INVALID_DISPLAY) {
                return null;
            }
            var dm = getDisplayManager();
            if (dm == null) {
                return null;
            }
            var display = dm.getDisplay(displayId);
            if (display == null || !isDisplayAllowed(display, this)) {
                return null;
            }
            return false;
            return wrapDmDisplay(display, DisplayIsEnabled.UNKNOWN);
        }

        /**
@@ -206,22 +227,6 @@ public class ExternalDisplaySettingsConfiguration {
            return true;
        }

        /**
         * @param displayId which must be returned
         * @return display object for the displayId
         */
        @Nullable
        public Display getDisplay(int displayId) {
            if (displayId == INVALID_DISPLAY) {
                return null;
            }
            var dm = getDisplayManager();
            if (dm == null) {
                return null;
            }
            return dm.getDisplay(displayId);
        }

        /**
         * @return handler
         */
@@ -323,8 +328,7 @@ public class ExternalDisplaySettingsConfiguration {
                || flags.displayTopologyPaneInDisplayList();
    }

    static boolean isDisplayAllowed(@NonNull Display display,
            @NonNull SystemServicesProvider props) {
    private static boolean isDisplayAllowed(Display display, SystemServicesProvider props) {
        return display.getType() == Display.TYPE_EXTERNAL
                || display.getType() == Display.TYPE_OVERLAY
                || isVirtualDisplayAllowed(display, props);
@@ -334,7 +338,7 @@ public class ExternalDisplaySettingsConfiguration {
        return injector != null && injector.getFlags().displayTopologyPaneInDisplayList();
    }

    static boolean isVirtualDisplayAllowed(@NonNull Display display,
    private static boolean isVirtualDisplayAllowed(@NonNull Display display,
            @NonNull SystemServicesProvider properties) {
        var sysProp = properties.getSystemProperty(VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY);
        return !sysProp.isEmpty() && display.getType() == Display.TYPE_VIRTUAL
+4 −12
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.settings.connecteddevice.display;

import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
@@ -134,19 +132,13 @@ public class ExternalDisplayUpdater {
            return null;
        }

        for (var display : mInjector.getEnabledDisplays()) {
            if (display != null && isDisplayAllowed(display, mInjector)) {
        var allDisplays = mInjector.getConnectedDisplays();
        for (var display : allDisplays) {
            if (display.isEnabled() == DisplayIsEnabled.YES) {
                return context.getString(R.string.external_display_on);
            }
        }

        for (var display : mInjector.getAllDisplays()) {
            if (display != null && isDisplayAllowed(display, mInjector)) {
                return context.getString(R.string.external_display_off);
            }
        }

        return null;
        return allDisplays.isEmpty() ? null : context.getString(R.string.external_display_off);
    }

    /**
+12 −13
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE;
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;

import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -29,7 +28,6 @@ import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
import android.view.Display.Mode;
import android.view.View;
import android.widget.TextView;
@@ -50,6 +48,7 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase {
    private static final String TAG = "ResolutionPreference";
@@ -164,7 +163,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase
            return;
        }
        var display = mInjector.getDisplay(getDisplayIdArg());
        if (display == null || !isDisplayAllowed(display, mInjector)) {
        if (display == null) {
            screen.removeAll();
            mTopOptionsPreference = null;
            mMoreOptionsPreference = null;
@@ -210,9 +209,9 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase
    }

    private void addRemainingPreferences(@NonNull Context context,
            @NonNull PreferenceCategory group, @NonNull Display display,
            boolean isSelectedModeFound, @NonNull Mode[] moreModes) {
        if (moreModes.length == 0) {
            @NonNull PreferenceCategory group, @NonNull DisplayDevice display,
            boolean isSelectedModeFound, @NonNull List<Mode> moreModes) {
        if (moreModes.isEmpty()) {
            return;
        }
        mMoreOptionsExpanded |= !isSelectedModeFound;
@@ -220,12 +219,12 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase
        addModePreferences(context, group, moreModes, /*checkMode=*/ null, display);
    }

    private Pair<Boolean, Mode[]> addModePreferences(@NonNull Context context,
    private Pair<Boolean, List<Mode>> addModePreferences(@NonNull Context context,
            @NonNull PreferenceGroup group,
            @NonNull Mode[] modes,
            @NonNull List<Mode> modes,
            @Nullable ToBooleanFunction<Mode> checkMode,
            @NonNull Display display) {
        Display.Mode curMode = display.getMode();
            @NonNull DisplayDevice display) {
        Mode curMode = display.getMode();
        var currentResolution = curMode.getPhysicalWidth() + "x" + curMode.getPhysicalHeight();
        var rotatedResolution = curMode.getPhysicalHeight() + "x" + curMode.getPhysicalWidth();
        var skippedModes = new ArrayList<Mode>();
@@ -260,7 +259,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase
            isAnyOfModesSelected |= isCurrentMode;
            group.addPreference(pref);
        }
        return new Pair<>(isAnyOfModesSelected, skippedModes.toArray(Mode.EMPTY_ARRAY));
        return new Pair<>(isAnyOfModesSelected, skippedModes);
    }

    private boolean isTopMode(@NonNull Mode mode) {
@@ -309,7 +308,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase
    }

    private void onDisplayModeClicked(@NonNull SelectorWithWidgetPreference preference,
            @NonNull Display display) {
            @NonNull DisplayDevice display) {
        if (mInjector == null) {
            return;
        }
@@ -319,7 +318,7 @@ public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase
        for (var mode : display.getSupportedModes()) {
            if (mode.getPhysicalWidth() == width && mode.getPhysicalHeight() == height
                        && isAllowedMode(mode)) {
                mInjector.setUserPreferredDisplayMode(display.getDisplayId(), mode);
                mInjector.setUserPreferredDisplayMode(display.getId(), mode);
                return;
            }
        }
Loading