Merge "Added External Display settings page" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
b3926bdf35
94
src/com/android/settings/SettingsPreferenceFragmentBase.java
Normal file
94
src/com/android/settings/SettingsPreferenceFragmentBase.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settingslib.search.Indexable;
|
||||
|
||||
/**
|
||||
* Base class for fragment suitable for unit testing.
|
||||
*/
|
||||
public abstract class SettingsPreferenceFragmentBase extends SettingsPreferenceFragment
|
||||
implements Indexable {
|
||||
@Override
|
||||
@SuppressWarnings({"RequiresNullabilityAnnotation"})
|
||||
public void onCreate(final Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
onCreateCallback(icicle);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"RequiresNullabilityAnnotation"})
|
||||
public void onActivityCreated(final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
onActivityCreatedCallback(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
onSaveInstanceStateCallback(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
onStartCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
onStopCallback();
|
||||
}
|
||||
|
||||
protected Activity getCurrentActivity() {
|
||||
return getActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called from {@link #onCreate}
|
||||
*/
|
||||
public abstract void onCreateCallback(@Nullable Bundle icicle);
|
||||
|
||||
/**
|
||||
* Callback called from {@link #onActivityCreated}
|
||||
*/
|
||||
public abstract void onActivityCreatedCallback(@Nullable Bundle savedInstanceState);
|
||||
|
||||
/**
|
||||
* Callback called from {@link #onStart}
|
||||
*/
|
||||
public abstract void onStartCallback();
|
||||
|
||||
/**
|
||||
* Callback called from {@link #onStop}
|
||||
*/
|
||||
public abstract void onStopCallback();
|
||||
|
||||
/**
|
||||
* Callback called from {@link #onSaveInstanceState}
|
||||
*/
|
||||
public void onSaveInstanceStateCallback(@NonNull final Bundle outState) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.settings.connecteddevice;
|
||||
|
||||
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isExternalDisplaySettingsPageEnabled;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.input.InputManager;
|
||||
@@ -22,6 +24,8 @@ import android.util.FeatureFlagUtils;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
@@ -31,12 +35,15 @@ import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
|
||||
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
|
||||
import com.android.settings.bluetooth.Utils;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplayUpdater;
|
||||
import com.android.settings.connecteddevice.dock.DockUpdater;
|
||||
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
|
||||
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.PreferenceControllerMixin;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.flags.FeatureFlags;
|
||||
import com.android.settings.flags.FeatureFlagsImpl;
|
||||
import com.android.settings.flags.Flags;
|
||||
import com.android.settings.overlay.DockUpdaterFeatureProvider;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
@@ -64,6 +71,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
|
||||
@VisibleForTesting
|
||||
PreferenceGroup mPreferenceGroup;
|
||||
@Nullable
|
||||
private ExternalDisplayUpdater mExternalDisplayUpdater;
|
||||
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
|
||||
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
|
||||
private DockUpdater mConnectedDockUpdater;
|
||||
@@ -71,6 +80,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
private final PackageManager mPackageManager;
|
||||
private final InputManager mInputManager;
|
||||
private final LocalBluetoothManager mLocalBluetoothManager;
|
||||
@NonNull
|
||||
private final FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
|
||||
|
||||
public ConnectedDeviceGroupController(Context context) {
|
||||
super(context, KEY);
|
||||
@@ -81,6 +92,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (mExternalDisplayUpdater != null) {
|
||||
mExternalDisplayUpdater.registerCallback();
|
||||
}
|
||||
|
||||
if (mBluetoothDeviceUpdater != null) {
|
||||
mBluetoothDeviceUpdater.registerCallback();
|
||||
mBluetoothDeviceUpdater.refreshPreference();
|
||||
@@ -101,6 +116,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (mExternalDisplayUpdater != null) {
|
||||
mExternalDisplayUpdater.unregisterCallback();
|
||||
}
|
||||
|
||||
if (mBluetoothDeviceUpdater != null) {
|
||||
mBluetoothDeviceUpdater.unregisterCallback();
|
||||
}
|
||||
@@ -127,6 +146,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
|
||||
if (isAvailable()) {
|
||||
final Context context = screen.getContext();
|
||||
if (mExternalDisplayUpdater != null) {
|
||||
mExternalDisplayUpdater.initPreference(context);
|
||||
}
|
||||
|
||||
if (mBluetoothDeviceUpdater != null) {
|
||||
mBluetoothDeviceUpdater.setPrefContext(context);
|
||||
mBluetoothDeviceUpdater.forceUpdate();
|
||||
@@ -150,7 +173,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return (hasBluetoothFeature()
|
||||
return (hasExternalDisplayFeature()
|
||||
|| hasBluetoothFeature()
|
||||
|| hasUsbFeature()
|
||||
|| hasUsiStylusFeature()
|
||||
|| mConnectedDockUpdater != null)
|
||||
@@ -180,11 +204,13 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void init(BluetoothDeviceUpdater bluetoothDeviceUpdater,
|
||||
void init(@Nullable ExternalDisplayUpdater externalDisplayUpdater,
|
||||
BluetoothDeviceUpdater bluetoothDeviceUpdater,
|
||||
ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater,
|
||||
DockUpdater connectedDockUpdater,
|
||||
StylusDeviceUpdater connectedStylusDeviceUpdater) {
|
||||
|
||||
mExternalDisplayUpdater = externalDisplayUpdater;
|
||||
mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
|
||||
mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
|
||||
mConnectedDockUpdater = connectedDockUpdater;
|
||||
@@ -197,7 +223,10 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider();
|
||||
final DockUpdater connectedDockUpdater =
|
||||
dockUpdaterFeatureProvider.getConnectedDockUpdater(context, this);
|
||||
init(hasBluetoothFeature()
|
||||
init(hasExternalDisplayFeature()
|
||||
? new ExternalDisplayUpdater(this, fragment.getMetricsCategory())
|
||||
: null,
|
||||
hasBluetoothFeature()
|
||||
? new ConnectedBluetoothDeviceUpdater(context, this,
|
||||
fragment.getMetricsCategory())
|
||||
: null,
|
||||
@@ -210,6 +239,19 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
|
||||
: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return trunk stable feature flags.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@NonNull
|
||||
public FeatureFlags getFeatureFlags() {
|
||||
return mFeatureFlags;
|
||||
}
|
||||
|
||||
private boolean hasExternalDisplayFeature() {
|
||||
return isExternalDisplaySettingsPageEnabled(getFeatureFlags());
|
||||
}
|
||||
|
||||
private boolean hasBluetoothFeature() {
|
||||
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
|
||||
}
|
||||
|
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 static android.view.Display.INVALID_DISPLAY;
|
||||
|
||||
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
|
||||
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
|
||||
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.isUseDisplaySettingEnabled;
|
||||
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isResolutionSettingEnabled;
|
||||
import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isRotationSettingEnabled;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsPreferenceFragmentBase;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.search.BaseSearchIndexProvider;
|
||||
import com.android.settingslib.search.Indexable;
|
||||
import com.android.settingslib.search.SearchIndexable;
|
||||
import com.android.settingslib.widget.FooterPreference;
|
||||
import com.android.settingslib.widget.IllustrationPreference;
|
||||
import com.android.settingslib.widget.MainSwitchPreference;
|
||||
import com.android.settingslib.widget.TwoTargetPreference;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Settings screen for External Displays configuration and connection management.
|
||||
*/
|
||||
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||
public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmentBase
|
||||
implements Indexable {
|
||||
static final int EXTERNAL_DISPLAY_SETTINGS_RESOURCE = R.xml.external_display_settings;
|
||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||
new BaseSearchIndexProvider(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
|
||||
static final String DISPLAYS_LIST_PREFERENCE_KEY = "displays_list_preference";
|
||||
static final String EXTERNAL_DISPLAY_USE_PREFERENCE_KEY = "external_display_use_preference";
|
||||
static final String EXTERNAL_DISPLAY_ROTATION_KEY = "external_display_rotation";
|
||||
static final String EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY = "external_display_resolution";
|
||||
static final int EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE =
|
||||
R.string.external_display_change_resolution_footer_title;
|
||||
static final int EXTERNAL_DISPLAY_LANDSCAPE_DRAWABLE =
|
||||
R.drawable.external_display_mirror_landscape;
|
||||
static final int EXTERANAL_DISPLAY_TITLE_RESOURCE =
|
||||
R.string.external_display_settings_title;
|
||||
static final int EXTERNAL_DISPLAY_USE_TITLE_RESOURCE =
|
||||
R.string.external_display_use_title;
|
||||
static final int EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE =
|
||||
R.string.external_display_not_found_footer_title;
|
||||
static final int EXTERNAL_DISPLAY_PORTRAIT_DRAWABLE =
|
||||
R.drawable.external_display_mirror_portrait;
|
||||
static final int EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE =
|
||||
R.string.external_display_rotation;
|
||||
static final int EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE =
|
||||
R.string.external_display_resolution_settings_title;
|
||||
@VisibleForTesting
|
||||
static final String PREVIOUSLY_SHOWN_LIST_KEY = "mPreviouslyShownListOfDisplays";
|
||||
private boolean mStarted;
|
||||
@Nullable
|
||||
private MainSwitchPreference mUseDisplayPref;
|
||||
@Nullable
|
||||
private IllustrationPreference mImagePreference;
|
||||
@Nullable
|
||||
private Preference mResolutionPreference;
|
||||
@Nullable
|
||||
private ListPreference mRotationPref;
|
||||
@Nullable
|
||||
private FooterPreference mFooterPreference;
|
||||
@Nullable
|
||||
private PreferenceCategory mDisplaysPreference;
|
||||
@Nullable
|
||||
private Injector mInjector;
|
||||
@Nullable
|
||||
private String[] mRotationEntries;
|
||||
@Nullable
|
||||
private String[] mRotationEntriesValues;
|
||||
@NonNull
|
||||
private final Runnable mUpdateRunnable = this::update;
|
||||
private final DisplayListener mListener = new DisplayListener() {
|
||||
@Override
|
||||
public void update(int displayId) {
|
||||
scheduleUpdate();
|
||||
}
|
||||
};
|
||||
private boolean mPreviouslyShownListOfDisplays;
|
||||
|
||||
public ExternalDisplayPreferenceFragment() {}
|
||||
|
||||
@VisibleForTesting
|
||||
ExternalDisplayPreferenceFragment(@NonNull Injector injector) {
|
||||
mInjector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return EXTERNAL_DISPLAY_HELP_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceStateCallback(@NonNull Bundle outState) {
|
||||
outState.putSerializable(PREVIOUSLY_SHOWN_LIST_KEY,
|
||||
(Serializable) mPreviouslyShownListOfDisplays);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateCallback(@Nullable Bundle icicle) {
|
||||
if (mInjector == null) {
|
||||
mInjector = new Injector(getPrefContext());
|
||||
}
|
||||
addPreferencesFromResource(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreatedCallback(@Nullable Bundle savedInstanceState) {
|
||||
restoreState(savedInstanceState);
|
||||
View view = getView();
|
||||
TextView emptyView = null;
|
||||
if (view != null) {
|
||||
emptyView = (TextView) view.findViewById(android.R.id.empty);
|
||||
}
|
||||
if (emptyView != null) {
|
||||
emptyView.setText(EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE);
|
||||
setEmptyView(emptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartCallback() {
|
||||
mStarted = true;
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
mInjector.registerDisplayListener(mListener);
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopCallback() {
|
||||
mStarted = false;
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
mInjector.unregisterDisplayListener(mListener);
|
||||
unscheduleUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return id of the preference.
|
||||
*/
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return EXTERNAL_DISPLAY_SETTINGS_RESOURCE;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void launchResolutionSelector(@NonNull final Context context, final int displayId) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putInt(DISPLAY_ID_ARG, displayId);
|
||||
new SubSettingLauncher(context)
|
||||
.setDestination(ResolutionPreferenceFragment.class.getName())
|
||||
.setArguments(args)
|
||||
.setSourceMetricsCategory(getMetricsCategory()).launch();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void launchDisplaySettings(final int displayId) {
|
||||
final Bundle args = new Bundle();
|
||||
var context = getPrefContext();
|
||||
args.putInt(DISPLAY_ID_ARG, displayId);
|
||||
new SubSettingLauncher(context)
|
||||
.setDestination(this.getClass().getName())
|
||||
.setArguments(args)
|
||||
.setSourceMetricsCategory(getMetricsCategory()).launch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preference for the footer.
|
||||
*/
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
FooterPreference getFooterPreference(@NonNull Context context) {
|
||||
if (mFooterPreference == null) {
|
||||
mFooterPreference = new FooterPreference(context);
|
||||
mFooterPreference.setPersistent(false);
|
||||
}
|
||||
return mFooterPreference;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
ListPreference getRotationPreference(@NonNull Context context) {
|
||||
if (mRotationPref == null) {
|
||||
mRotationPref = new ListPreference(context);
|
||||
mRotationPref.setPersistent(false);
|
||||
}
|
||||
return mRotationPref;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
Preference getResolutionPreference(@NonNull Context context) {
|
||||
if (mResolutionPreference == null) {
|
||||
mResolutionPreference = new Preference(context);
|
||||
mResolutionPreference.setPersistent(false);
|
||||
}
|
||||
return mResolutionPreference;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
MainSwitchPreference getUseDisplayPreference(@NonNull Context context) {
|
||||
if (mUseDisplayPref == null) {
|
||||
mUseDisplayPref = new MainSwitchPreference(context);
|
||||
mUseDisplayPref.setPersistent(false);
|
||||
}
|
||||
return mUseDisplayPref;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
IllustrationPreference getIllustrationPreference(@NonNull Context context) {
|
||||
if (mImagePreference == null) {
|
||||
mImagePreference = new IllustrationPreference(context);
|
||||
mImagePreference.setPersistent(false);
|
||||
}
|
||||
return mImagePreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return display id argument of this settings page.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected int getDisplayIdArg() {
|
||||
var args = getArguments();
|
||||
return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PreferenceCategory getDisplaysListPreference(@NonNull Context context) {
|
||||
if (mDisplaysPreference == null) {
|
||||
mDisplaysPreference = new PreferenceCategory(context);
|
||||
mDisplaysPreference.setPersistent(false);
|
||||
}
|
||||
return mDisplaysPreference;
|
||||
}
|
||||
|
||||
private void restoreState(@Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState == null) {
|
||||
return;
|
||||
}
|
||||
mPreviouslyShownListOfDisplays = Boolean.TRUE.equals(savedInstanceState.getSerializable(
|
||||
PREVIOUSLY_SHOWN_LIST_KEY, Boolean.class));
|
||||
}
|
||||
|
||||
private void update() {
|
||||
final var screen = getPreferenceScreen();
|
||||
if (screen == null || mInjector == null || mInjector.getContext() == null) {
|
||||
return;
|
||||
}
|
||||
screen.removeAll();
|
||||
updateScreenForDisplayId(getDisplayIdArg(), screen, mInjector.getContext());
|
||||
}
|
||||
|
||||
private void updateScreenForDisplayId(final int displayId,
|
||||
@NonNull final PreferenceScreen screen, @NonNull Context context) {
|
||||
final var displaysToShow = getDisplaysToShow(displayId);
|
||||
if (displaysToShow.isEmpty() && displayId == INVALID_DISPLAY) {
|
||||
showTextWhenNoDisplaysToShow(screen, context);
|
||||
} else if (displaysToShow.size() == 1
|
||||
&& ((displayId == INVALID_DISPLAY && !mPreviouslyShownListOfDisplays)
|
||||
|| displaysToShow.get(0).getDisplayId() == displayId)) {
|
||||
showDisplaySettings(displaysToShow.get(0), screen, context);
|
||||
} else if (displayId == INVALID_DISPLAY) {
|
||||
// If ever shown a list of displays - keep showing it for consistency after
|
||||
// disconnecting one of the displays, and only one display is left.
|
||||
mPreviouslyShownListOfDisplays = true;
|
||||
showDisplaysList(displaysToShow, screen, context);
|
||||
}
|
||||
updateSettingsTitle(displaysToShow, displayId);
|
||||
}
|
||||
|
||||
private void updateSettingsTitle(@NonNull final List<Display> displaysToShow, int displayId) {
|
||||
final Activity activity = getCurrentActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
if (displaysToShow.size() == 1 && displaysToShow.get(0).getDisplayId() == displayId) {
|
||||
var displayName = displaysToShow.get(0).getName();
|
||||
if (!displayName.isEmpty()) {
|
||||
activity.setTitle(displayName.substring(0, Math.min(displayName.length(), 40)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
activity.setTitle(EXTERANAL_DISPLAY_TITLE_RESOURCE);
|
||||
}
|
||||
|
||||
private void showTextWhenNoDisplaysToShow(@NonNull final PreferenceScreen screen,
|
||||
@NonNull Context context) {
|
||||
if (isUseDisplaySettingEnabled(mInjector)) {
|
||||
screen.addPreference(updateUseDisplayPreferenceNoDisplaysFound(context));
|
||||
}
|
||||
screen.addPreference(updateFooterPreference(context,
|
||||
EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE));
|
||||
}
|
||||
|
||||
private void showDisplaySettings(@NonNull Display display, @NonNull PreferenceScreen screen,
|
||||
@NonNull Context context) {
|
||||
final var isEnabled = mInjector != null && mInjector.isDisplayEnabled(display);
|
||||
if (isUseDisplaySettingEnabled(mInjector)) {
|
||||
screen.addPreference(updateUseDisplayPreference(context, display, isEnabled));
|
||||
}
|
||||
if (!isEnabled) {
|
||||
// Skip all other settings
|
||||
return;
|
||||
}
|
||||
final var displayRotation = getDisplayRotation(display.getDisplayId());
|
||||
screen.addPreference(updateIllustrationImage(context, displayRotation));
|
||||
screen.addPreference(updateResolutionPreference(context, display));
|
||||
screen.addPreference(updateRotationPreference(context, display, displayRotation));
|
||||
if (isResolutionSettingEnabled(mInjector)) {
|
||||
screen.addPreference(updateFooterPreference(context,
|
||||
EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE));
|
||||
}
|
||||
}
|
||||
|
||||
private void showDisplaysList(@NonNull List<Display> displaysToShow,
|
||||
@NonNull PreferenceScreen screen, @NonNull Context context) {
|
||||
var pref = getDisplaysListPreference(context);
|
||||
pref.setKey(DISPLAYS_LIST_PREFERENCE_KEY);
|
||||
pref.removeAll();
|
||||
if (!displaysToShow.isEmpty()) {
|
||||
screen.addPreference(pref);
|
||||
}
|
||||
for (var display : displaysToShow) {
|
||||
pref.addPreference(new DisplayPreference(context, display));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Display> getDisplaysToShow(int displayIdToShow) {
|
||||
if (mInjector == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (displayIdToShow != INVALID_DISPLAY) {
|
||||
var display = mInjector.getDisplay(displayIdToShow);
|
||||
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||
return List.of(display);
|
||||
}
|
||||
}
|
||||
var displaysToShow = new ArrayList<Display>();
|
||||
for (var display : mInjector.getAllDisplays()) {
|
||||
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||
displaysToShow.add(display);
|
||||
}
|
||||
}
|
||||
return displaysToShow;
|
||||
}
|
||||
|
||||
private Preference updateUseDisplayPreferenceNoDisplaysFound(@NonNull Context context) {
|
||||
final var pref = getUseDisplayPreference(context);
|
||||
pref.setKey(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||
pref.setTitle(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE);
|
||||
pref.setChecked(false);
|
||||
pref.setEnabled(false);
|
||||
pref.setOnPreferenceChangeListener(null);
|
||||
return pref;
|
||||
}
|
||||
|
||||
private Preference updateUseDisplayPreference(@NonNull final Context context,
|
||||
@NonNull final Display display, boolean isEnabled) {
|
||||
final var pref = getUseDisplayPreference(context);
|
||||
pref.setKey(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
|
||||
pref.setTitle(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE);
|
||||
pref.setChecked(isEnabled);
|
||||
pref.setEnabled(true);
|
||||
pref.setOnPreferenceChangeListener((p, newValue) -> {
|
||||
writePreferenceClickMetric(p);
|
||||
final boolean result;
|
||||
if (mInjector == null) {
|
||||
return false;
|
||||
}
|
||||
if ((Boolean) newValue) {
|
||||
result = mInjector.enableConnectedDisplay(display.getDisplayId());
|
||||
} else {
|
||||
result = mInjector.disableConnectedDisplay(display.getDisplayId());
|
||||
}
|
||||
if (result) {
|
||||
pref.setChecked((Boolean) newValue);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return pref;
|
||||
}
|
||||
|
||||
private Preference updateIllustrationImage(@NonNull final Context context,
|
||||
final int displayRotation) {
|
||||
var pref = getIllustrationPreference(context);
|
||||
if (displayRotation % 2 == 0) {
|
||||
pref.setLottieAnimationResId(EXTERNAL_DISPLAY_PORTRAIT_DRAWABLE);
|
||||
} else {
|
||||
pref.setLottieAnimationResId(EXTERNAL_DISPLAY_LANDSCAPE_DRAWABLE);
|
||||
}
|
||||
return pref;
|
||||
}
|
||||
|
||||
private Preference updateFooterPreference(@NonNull final Context context, final int title) {
|
||||
var pref = getFooterPreference(context);
|
||||
pref.setTitle(title);
|
||||
return pref;
|
||||
}
|
||||
|
||||
private Preference updateRotationPreference(@NonNull final Context context,
|
||||
@NonNull final Display display, final int displayRotation) {
|
||||
var pref = getRotationPreference(context);
|
||||
pref.setKey(EXTERNAL_DISPLAY_ROTATION_KEY);
|
||||
pref.setTitle(EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE);
|
||||
if (mRotationEntries == null || mRotationEntriesValues == null) {
|
||||
mRotationEntries = new String[] {
|
||||
context.getString(R.string.external_display_standard_rotation),
|
||||
context.getString(R.string.external_display_rotation_90),
|
||||
context.getString(R.string.external_display_rotation_180),
|
||||
context.getString(R.string.external_display_rotation_270)};
|
||||
mRotationEntriesValues = new String[] {"0", "1", "2", "3"};
|
||||
}
|
||||
pref.setEntries(mRotationEntries);
|
||||
pref.setEntryValues(mRotationEntriesValues);
|
||||
pref.setValueIndex(displayRotation);
|
||||
pref.setSummary(mRotationEntries[displayRotation]);
|
||||
pref.setOnPreferenceChangeListener((p, newValue) -> {
|
||||
writePreferenceClickMetric(p);
|
||||
var rotation = Integer.parseInt((String) newValue);
|
||||
var displayId = display.getDisplayId();
|
||||
if (mInjector == null || !mInjector.freezeDisplayRotation(displayId, rotation)) {
|
||||
return false;
|
||||
}
|
||||
pref.setValueIndex(rotation);
|
||||
return true;
|
||||
});
|
||||
pref.setEnabled(isRotationSettingEnabled(mInjector));
|
||||
return pref;
|
||||
}
|
||||
|
||||
private Preference updateResolutionPreference(@NonNull final Context context,
|
||||
@NonNull final Display display) {
|
||||
var pref = getResolutionPreference(context);
|
||||
pref.setKey(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
|
||||
pref.setTitle(EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE);
|
||||
pref.setSummary(display.getMode().getPhysicalWidth() + " x "
|
||||
+ display.getMode().getPhysicalHeight());
|
||||
pref.setOnPreferenceClickListener((Preference p) -> {
|
||||
writePreferenceClickMetric(p);
|
||||
launchResolutionSelector(context, display.getDisplayId());
|
||||
return true;
|
||||
});
|
||||
pref.setEnabled(isResolutionSettingEnabled(mInjector));
|
||||
return pref;
|
||||
}
|
||||
|
||||
private int getDisplayRotation(int displayId) {
|
||||
if (mInjector == null) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(3, Math.max(0, mInjector.getDisplayUserRotation(displayId)));
|
||||
}
|
||||
|
||||
private void scheduleUpdate() {
|
||||
if (mInjector == null || !mStarted) {
|
||||
return;
|
||||
}
|
||||
unscheduleUpdate();
|
||||
mInjector.getHandler().post(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void unscheduleUpdate() {
|
||||
if (mInjector == null || !mStarted) {
|
||||
return;
|
||||
}
|
||||
mInjector.getHandler().removeCallbacks(mUpdateRunnable);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class DisplayPreference extends TwoTargetPreference
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
private final int mDisplayId;
|
||||
|
||||
DisplayPreference(@NonNull final Context context, @NonNull final Display display) {
|
||||
super(context);
|
||||
mDisplayId = display.getDisplayId();
|
||||
setPersistent(false);
|
||||
setKey("display_id_" + mDisplayId);
|
||||
setTitle(display.getName());
|
||||
setSummary(display.getMode().getPhysicalWidth() + " x "
|
||||
+ display.getMode().getPhysicalHeight());
|
||||
setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
launchDisplaySettings(mDisplayId);
|
||||
writePreferenceClickMetric(preference);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 static android.content.Context.DISPLAY_SERVICE;
|
||||
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
|
||||
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
|
||||
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED;
|
||||
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
|
||||
import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
|
||||
import static android.view.Display.INVALID_DISPLAY;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.DisplayManagerGlobal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemProperties;
|
||||
import android.view.Display;
|
||||
import android.view.Display.Mode;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.flags.FeatureFlags;
|
||||
import com.android.settings.flags.FeatureFlagsImpl;
|
||||
|
||||
public class ExternalDisplaySettingsConfiguration {
|
||||
static final String VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY =
|
||||
"persist.demo.userrotation.package_name";
|
||||
static final String DISPLAY_ID_ARG = "display_id";
|
||||
static final int EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE = R.string.external_display_not_found;
|
||||
static final int EXTERNAL_DISPLAY_HELP_URL = R.string.help_url_external_display;
|
||||
|
||||
public static class SystemServicesProvider {
|
||||
@Nullable
|
||||
private IWindowManager mWindowManager;
|
||||
@Nullable
|
||||
private DisplayManager mDisplayManager;
|
||||
@Nullable
|
||||
protected Context mContext;
|
||||
/**
|
||||
* @param name of a system property.
|
||||
* @return the value of the system property.
|
||||
*/
|
||||
@NonNull
|
||||
public String getSystemProperty(@NonNull String name) {
|
||||
return SystemProperties.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return public Display manager.
|
||||
*/
|
||||
@Nullable
|
||||
public DisplayManager getDisplayManager() {
|
||||
if (mDisplayManager == null && getContext() != null) {
|
||||
mDisplayManager = (DisplayManager) getContext().getSystemService(DISPLAY_SERVICE);
|
||||
}
|
||||
return mDisplayManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return internal IWindowManager
|
||||
*/
|
||||
@Nullable
|
||||
public IWindowManager getWindowManager() {
|
||||
if (mWindowManager == null) {
|
||||
mWindowManager = WindowManagerGlobal.getWindowManagerService();
|
||||
}
|
||||
return mWindowManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return context.
|
||||
*/
|
||||
@Nullable
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Injector extends SystemServicesProvider {
|
||||
@NonNull
|
||||
private final FeatureFlags mFlags;
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
|
||||
Injector(@Nullable Context context) {
|
||||
this(context, new FeatureFlagsImpl(), new Handler(Looper.getMainLooper()));
|
||||
}
|
||||
|
||||
Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
|
||||
mContext = context;
|
||||
mFlags = flags;
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all displays including disabled.
|
||||
*/
|
||||
@NonNull
|
||||
public Display[] getAllDisplays() {
|
||||
var dm = getDisplayManager();
|
||||
if (dm == null) {
|
||||
return new Display[0];
|
||||
}
|
||||
return dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return enabled displays only.
|
||||
*/
|
||||
@NonNull
|
||||
public Display[] getEnabledDisplays() {
|
||||
var dm = getDisplayManager();
|
||||
if (dm == null) {
|
||||
return new Display[0];
|
||||
}
|
||||
return dm.getDisplays();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the display is enabled
|
||||
*/
|
||||
public boolean isDisplayEnabled(@NonNull Display display) {
|
||||
for (var enabledDisplay : getEnabledDisplays()) {
|
||||
if (enabledDisplay.getDisplayId() == display.getDisplayId()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register display listener.
|
||||
*/
|
||||
public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
|
||||
var dm = getDisplayManager();
|
||||
if (dm == null) {
|
||||
return;
|
||||
}
|
||||
dm.registerDisplayListener(listener, mHandler, EVENT_FLAG_DISPLAY_ADDED
|
||||
| EVENT_FLAG_DISPLAY_CHANGED | EVENT_FLAG_DISPLAY_REMOVED
|
||||
| EVENT_FLAG_DISPLAY_CONNECTION_CHANGED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister display listener.
|
||||
*/
|
||||
public void unregisterDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
|
||||
var dm = getDisplayManager();
|
||||
if (dm == null) {
|
||||
return;
|
||||
}
|
||||
dm.unregisterDisplayListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return feature flags.
|
||||
*/
|
||||
@NonNull
|
||||
public FeatureFlags getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable connected display.
|
||||
*/
|
||||
public boolean enableConnectedDisplay(int displayId) {
|
||||
var dm = getDisplayManager();
|
||||
if (dm == null) {
|
||||
return false;
|
||||
}
|
||||
dm.enableConnectedDisplay(displayId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable connected display.
|
||||
*/
|
||||
public boolean disableConnectedDisplay(int displayId) {
|
||||
var dm = getDisplayManager();
|
||||
if (dm == null) {
|
||||
return false;
|
||||
}
|
||||
dm.disableConnectedDisplay(displayId);
|
||||
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
|
||||
*/
|
||||
@NonNull
|
||||
public Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display rotation
|
||||
* @param displayId display identifier
|
||||
* @return rotation
|
||||
*/
|
||||
public int getDisplayUserRotation(int displayId) {
|
||||
var wm = getWindowManager();
|
||||
if (wm == null) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return wm.getDisplayUserRotation(displayId);
|
||||
} catch (RemoteException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze rotation of the display in the specified rotation.
|
||||
* @param displayId display identifier
|
||||
* @param rotation [0, 1, 2, 3]
|
||||
* @return true if successful
|
||||
*/
|
||||
public boolean freezeDisplayRotation(int displayId, int rotation) {
|
||||
var wm = getWindowManager();
|
||||
if (wm == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
wm.freezeDisplayRotation(displayId, rotation,
|
||||
"ExternalDisplayPreferenceFragment");
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce display mode on the given display.
|
||||
*/
|
||||
public void setUserPreferredDisplayMode(int displayId, @NonNull Mode mode) {
|
||||
DisplayManagerGlobal.getInstance().setUserPreferredDisplayMode(displayId, mode);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class DisplayListener implements DisplayManager.DisplayListener {
|
||||
@Override
|
||||
public void onDisplayAdded(int displayId) {
|
||||
update(displayId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayRemoved(int displayId) {
|
||||
update(displayId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayChanged(int displayId) {
|
||||
update(displayId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayConnected(int displayId) {
|
||||
update(displayId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayDisconnected(int displayId) {
|
||||
update(displayId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from other listener methods to trigger update of the settings page.
|
||||
*/
|
||||
public abstract void update(int displayId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the settings page is enabled or not.
|
||||
*/
|
||||
public static boolean isExternalDisplaySettingsPageEnabled(@NonNull FeatureFlags flags) {
|
||||
return flags.rotationConnectedDisplaySetting()
|
||||
|| flags.resolutionAndEnableConnectedDisplaySetting();
|
||||
}
|
||||
|
||||
static boolean isDisplayAllowed(@NonNull Display display,
|
||||
@NonNull SystemServicesProvider props) {
|
||||
return display.getType() == Display.TYPE_EXTERNAL
|
||||
|| display.getType() == Display.TYPE_OVERLAY
|
||||
|| isVirtualDisplayAllowed(display, props);
|
||||
}
|
||||
|
||||
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
|
||||
&& sysProp.equals(display.getOwnerPackageName());
|
||||
}
|
||||
|
||||
static boolean isUseDisplaySettingEnabled(@Nullable Injector injector) {
|
||||
return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
|
||||
}
|
||||
|
||||
static boolean isResolutionSettingEnabled(@Nullable Injector injector) {
|
||||
return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
|
||||
}
|
||||
|
||||
static boolean isRotationSettingEnabled(@Nullable Injector injector) {
|
||||
return injector != null && injector.getFlags().rotationConnectedDisplaySetting();
|
||||
}
|
||||
}
|
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.connecteddevice.DevicePreferenceCallback;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
|
||||
public class ExternalDisplayUpdater {
|
||||
|
||||
private static final String PREF_KEY = "external_display_settings";
|
||||
private final int mMetricsCategory;
|
||||
@NonNull
|
||||
private final MetricsFeatureProvider mMetricsFeatureProvider;
|
||||
@NonNull
|
||||
private final Runnable mUpdateRunnable = this::update;
|
||||
@NonNull
|
||||
private final DevicePreferenceCallback mDevicePreferenceCallback;
|
||||
@Nullable
|
||||
private RestrictedPreference mPreference;
|
||||
@Nullable
|
||||
private Injector mInjector;
|
||||
private final DisplayListener mListener = new DisplayListener() {
|
||||
@Override
|
||||
public void update(int displayId) {
|
||||
scheduleUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
public ExternalDisplayUpdater(@NonNull DevicePreferenceCallback callback, int metricsCategory) {
|
||||
mDevicePreferenceCallback = callback;
|
||||
mMetricsCategory = metricsCategory;
|
||||
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context to generate the {@link Preference}, so it could get the correct theme.
|
||||
*/
|
||||
public void initPreference(@NonNull Context context) {
|
||||
initPreference(context, new Injector(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void initPreference(@NonNull Context context, Injector injector) {
|
||||
mInjector = injector;
|
||||
mPreference = new RestrictedPreference(context, null /* AttributeSet */);
|
||||
mPreference.setTitle(R.string.external_display_settings_title);
|
||||
mPreference.setSummary(getSummary());
|
||||
mPreference.setIcon(getDrawable(context));
|
||||
mPreference.setKey(PREF_KEY);
|
||||
mPreference.setDisabledByAdmin(checkIfUsbDataSignalingIsDisabled(context));
|
||||
mPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||
mMetricsFeatureProvider.logClickedPreference(p, mMetricsCategory);
|
||||
// New version - uses a separate screen.
|
||||
new SubSettingLauncher(context)
|
||||
.setDestination(ExternalDisplayPreferenceFragment.class.getName())
|
||||
.setTitleRes(R.string.external_display_settings_title)
|
||||
.setSourceMetricsCategory(mMetricsCategory)
|
||||
.launch();
|
||||
return true;
|
||||
});
|
||||
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the display listener.
|
||||
*/
|
||||
public void unregisterCallback() {
|
||||
if (mInjector != null) {
|
||||
mInjector.unregisterDisplayListener(mListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the display listener.
|
||||
*/
|
||||
public void registerCallback() {
|
||||
if (mInjector != null) {
|
||||
mInjector.registerDisplayListener(mListener);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
protected RestrictedLockUtils.EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context) {
|
||||
return RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled(context,
|
||||
UserHandle.myUserId());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
protected Drawable getDrawable(Context context) {
|
||||
return context.getDrawable(R.drawable.ic_external_display_32dp);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected CharSequence getSummary() {
|
||||
if (mInjector == null) {
|
||||
return null;
|
||||
}
|
||||
var context = mInjector.getContext();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var display : mInjector.getEnabledDisplays()) {
|
||||
if (display != null && isDisplayAllowed(display, mInjector)) {
|
||||
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;
|
||||
}
|
||||
|
||||
private void scheduleUpdate() {
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
unscheduleUpdate();
|
||||
mInjector.getHandler().post(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void unscheduleUpdate() {
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
mInjector.getHandler().removeCallbacks(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
var summary = getSummary();
|
||||
if (mPreference == null) {
|
||||
return;
|
||||
}
|
||||
mPreference.setSummary(summary);
|
||||
if (summary != null) {
|
||||
mDevicePreferenceCallback.onDeviceAdded(mPreference);
|
||||
} else {
|
||||
mDevicePreferenceCallback.onDeviceRemoved(mPreference);
|
||||
}
|
||||
}
|
||||
}
|
7
src/com/android/settings/connecteddevice/display/OWNERS
Normal file
7
src/com/android/settings/connecteddevice/display/OWNERS
Normal file
@@ -0,0 +1,7 @@
|
||||
# Default reviewers for this and subdirectories.
|
||||
santoscordon@google.com
|
||||
petsjonkin@google.com
|
||||
flc@google.com
|
||||
wilczynskip@google.com
|
||||
brup@google.com
|
||||
olb@google.com
|
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 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;
|
||||
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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceGroup;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.internal.util.ToBooleanFunction;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsPreferenceFragmentBase;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
|
||||
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
|
||||
import com.android.settingslib.widget.SelectorWithWidgetPreference;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase {
|
||||
private static final String TAG = "ResolutionPreferenceFragment";
|
||||
static final int DEFAULT_LOW_REFRESH_RATE = 60;
|
||||
static final String MORE_OPTIONS_KEY = "more_options";
|
||||
static final String TOP_OPTIONS_KEY = "top_options";
|
||||
static final int MORE_OPTIONS_TITLE_RESOURCE =
|
||||
R.string.external_display_more_options_title;
|
||||
static final int EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE =
|
||||
R.xml.external_display_resolution_settings;
|
||||
@Nullable
|
||||
private Injector mInjector;
|
||||
@Nullable
|
||||
private PreferenceCategory mTopOptionsPreference;
|
||||
@Nullable
|
||||
private PreferenceCategory mMoreOptionsPreference;
|
||||
private boolean mStarted;
|
||||
private final HashSet<String> mResolutionPreferences = new HashSet<>();
|
||||
private int mExternalDisplayPeakWidth;
|
||||
private int mExternalDisplayPeakHeight;
|
||||
private int mExternalDisplayPeakRefreshRate;
|
||||
private boolean mRefreshRateSynchronizationEnabled;
|
||||
private boolean mMoreOptionsExpanded;
|
||||
private final Runnable mUpdateRunnable = this::update;
|
||||
private final DisplayListener mListener = new DisplayListener() {
|
||||
@Override
|
||||
public void update(int displayId) {
|
||||
scheduleUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHelpResource() {
|
||||
return EXTERNAL_DISPLAY_HELP_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateCallback(@Nullable Bundle icicle) {
|
||||
if (mInjector == null) {
|
||||
mInjector = new Injector(getPrefContext());
|
||||
}
|
||||
addPreferencesFromResource(EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE);
|
||||
updateDisplayModeLimits(mInjector.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreatedCallback(@Nullable Bundle savedInstanceState) {
|
||||
View view = getView();
|
||||
TextView emptyView = null;
|
||||
if (view != null) {
|
||||
emptyView = (TextView) view.findViewById(android.R.id.empty);
|
||||
}
|
||||
if (emptyView != null) {
|
||||
emptyView.setText(EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE);
|
||||
setEmptyView(emptyView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartCallback() {
|
||||
mStarted = true;
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
mInjector.registerDisplayListener(mListener);
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopCallback() {
|
||||
mStarted = false;
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
mInjector.unregisterDisplayListener(mListener);
|
||||
unscheduleUpdate();
|
||||
}
|
||||
|
||||
public ResolutionPreferenceFragment() {}
|
||||
|
||||
@VisibleForTesting
|
||||
ResolutionPreferenceFragment(@NonNull Injector injector) {
|
||||
mInjector = injector;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected int getDisplayIdArg() {
|
||||
var args = getArguments();
|
||||
return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@NonNull
|
||||
protected Resources getResources(@NonNull Context context) {
|
||||
return context.getResources();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
final PreferenceScreen screen = getPreferenceScreen();
|
||||
if (screen == null || mInjector == null) {
|
||||
return;
|
||||
}
|
||||
var context = mInjector.getContext();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
var display = mInjector.getDisplay(getDisplayIdArg());
|
||||
if (display == null || !isDisplayAllowed(display, mInjector)) {
|
||||
screen.removeAll();
|
||||
mTopOptionsPreference = null;
|
||||
mMoreOptionsPreference = null;
|
||||
return;
|
||||
}
|
||||
mResolutionPreferences.clear();
|
||||
var remainingModes = addModePreferences(context,
|
||||
getTopPreference(context, screen),
|
||||
display.getSupportedModes(), this::isTopMode, display);
|
||||
addRemainingPreferences(context,
|
||||
getMorePreference(context, screen),
|
||||
display, remainingModes.first, remainingModes.second);
|
||||
}
|
||||
|
||||
private PreferenceCategory getTopPreference(@NonNull Context context,
|
||||
@NonNull PreferenceScreen screen) {
|
||||
if (mTopOptionsPreference == null) {
|
||||
mTopOptionsPreference = new PreferenceCategory(context);
|
||||
mTopOptionsPreference.setPersistent(false);
|
||||
mTopOptionsPreference.setKey(TOP_OPTIONS_KEY);
|
||||
screen.addPreference(mTopOptionsPreference);
|
||||
} else {
|
||||
mTopOptionsPreference.removeAll();
|
||||
}
|
||||
return mTopOptionsPreference;
|
||||
}
|
||||
|
||||
private PreferenceCategory getMorePreference(@NonNull Context context,
|
||||
@NonNull PreferenceScreen screen) {
|
||||
if (mMoreOptionsPreference == null) {
|
||||
mMoreOptionsPreference = new PreferenceCategory(context);
|
||||
mMoreOptionsPreference.setPersistent(false);
|
||||
mMoreOptionsPreference.setTitle(MORE_OPTIONS_TITLE_RESOURCE);
|
||||
mMoreOptionsPreference.setOnExpandButtonClickListener(() -> {
|
||||
mMoreOptionsExpanded = true;
|
||||
});
|
||||
mMoreOptionsPreference.setKey(MORE_OPTIONS_KEY);
|
||||
screen.addPreference(mMoreOptionsPreference);
|
||||
} else {
|
||||
mMoreOptionsPreference.removeAll();
|
||||
}
|
||||
return mMoreOptionsPreference;
|
||||
}
|
||||
|
||||
private void addRemainingPreferences(@NonNull Context context,
|
||||
@NonNull PreferenceCategory group, @NonNull Display display,
|
||||
boolean isSelectedModeFound, @NonNull Mode[] moreModes) {
|
||||
if (moreModes.length == 0) {
|
||||
return;
|
||||
}
|
||||
mMoreOptionsExpanded |= !isSelectedModeFound;
|
||||
group.setInitialExpandedChildrenCount(mMoreOptionsExpanded ? Integer.MAX_VALUE : 0);
|
||||
addModePreferences(context, group, moreModes, /*checkMode=*/ null, display);
|
||||
}
|
||||
|
||||
private Pair<Boolean, Mode[]> addModePreferences(@NonNull Context context,
|
||||
@NonNull PreferenceGroup group,
|
||||
@NonNull Mode[] modes,
|
||||
@Nullable ToBooleanFunction<Mode> checkMode,
|
||||
@NonNull Display display) {
|
||||
Display.Mode curMode = display.getMode();
|
||||
var currentResolution = curMode.getPhysicalWidth() + "x" + curMode.getPhysicalHeight();
|
||||
var rotatedResolution = curMode.getPhysicalHeight() + "x" + curMode.getPhysicalWidth();
|
||||
var skippedModes = new ArrayList<Mode>();
|
||||
var isAnyOfModesSelected = false;
|
||||
for (var mode : modes) {
|
||||
var modeStr = mode.getPhysicalWidth() + "x" + mode.getPhysicalHeight();
|
||||
SelectorWithWidgetPreference pref = group.findPreference(modeStr);
|
||||
if (pref != null) {
|
||||
continue;
|
||||
}
|
||||
if (checkMode != null && !checkMode.apply(mode)) {
|
||||
skippedModes.add(mode);
|
||||
continue;
|
||||
}
|
||||
var isCurrentMode =
|
||||
currentResolution.equals(modeStr) || rotatedResolution.equals(modeStr);
|
||||
if (!isCurrentMode && !isAllowedMode(mode)) {
|
||||
continue;
|
||||
}
|
||||
if (mResolutionPreferences.contains(modeStr)) {
|
||||
// Added to "Top modes" already.
|
||||
continue;
|
||||
}
|
||||
mResolutionPreferences.add(modeStr);
|
||||
pref = new SelectorWithWidgetPreference(context);
|
||||
pref.setPersistent(false);
|
||||
pref.setKey(modeStr);
|
||||
pref.setTitle(mode.getPhysicalWidth() + " x " + mode.getPhysicalHeight());
|
||||
pref.setSingleLineTitle(true);
|
||||
pref.setOnClickListener(preference -> onDisplayModeClicked(preference, display));
|
||||
pref.setChecked(isCurrentMode);
|
||||
isAnyOfModesSelected |= isCurrentMode;
|
||||
group.addPreference(pref);
|
||||
}
|
||||
return new Pair<>(isAnyOfModesSelected, skippedModes.toArray(Mode.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
private boolean isTopMode(@NonNull Mode mode) {
|
||||
return mTopOptionsPreference != null
|
||||
&& mTopOptionsPreference.getPreferenceCount() < 3;
|
||||
}
|
||||
|
||||
private boolean isAllowedMode(@NonNull Mode mode) {
|
||||
if (mRefreshRateSynchronizationEnabled
|
||||
&& (mode.getRefreshRate() < DEFAULT_LOW_REFRESH_RATE - 1
|
||||
|| mode.getRefreshRate() > DEFAULT_LOW_REFRESH_RATE + 1)) {
|
||||
Log.d(TAG, mode + " refresh rate is out of synchronization range");
|
||||
return false;
|
||||
}
|
||||
if (mExternalDisplayPeakHeight > 0
|
||||
&& mode.getPhysicalHeight() > mExternalDisplayPeakHeight) {
|
||||
Log.d(TAG, mode + " height is above the allowed limit");
|
||||
return false;
|
||||
}
|
||||
if (mExternalDisplayPeakWidth > 0
|
||||
&& mode.getPhysicalWidth() > mExternalDisplayPeakWidth) {
|
||||
Log.d(TAG, mode + " width is above the allowed limit");
|
||||
return false;
|
||||
}
|
||||
if (mExternalDisplayPeakRefreshRate > 0
|
||||
&& mode.getRefreshRate() > mExternalDisplayPeakRefreshRate) {
|
||||
Log.d(TAG, mode + " refresh rate is above the allowed limit");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void scheduleUpdate() {
|
||||
if (mInjector == null || !mStarted) {
|
||||
return;
|
||||
}
|
||||
unscheduleUpdate();
|
||||
mInjector.getHandler().post(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void unscheduleUpdate() {
|
||||
if (mInjector == null || !mStarted) {
|
||||
return;
|
||||
}
|
||||
mInjector.getHandler().removeCallbacks(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void onDisplayModeClicked(@NonNull SelectorWithWidgetPreference preference,
|
||||
@NonNull Display display) {
|
||||
if (mInjector == null) {
|
||||
return;
|
||||
}
|
||||
String[] modeResolution = preference.getKey().split("x");
|
||||
int width = Integer.parseInt(modeResolution[0]);
|
||||
int height = Integer.parseInt(modeResolution[1]);
|
||||
for (var mode : display.getSupportedModes()) {
|
||||
if (mode.getPhysicalWidth() == width && mode.getPhysicalHeight() == height
|
||||
&& isAllowedMode(mode)) {
|
||||
mInjector.setUserPreferredDisplayMode(display.getDisplayId(), mode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDisplayModeLimits(@Nullable Context context) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
mExternalDisplayPeakRefreshRate = getResources(context).getInteger(
|
||||
com.android.internal.R.integer.config_externalDisplayPeakRefreshRate);
|
||||
mExternalDisplayPeakWidth = getResources(context).getInteger(
|
||||
com.android.internal.R.integer.config_externalDisplayPeakWidth);
|
||||
mExternalDisplayPeakHeight = getResources(context).getInteger(
|
||||
com.android.internal.R.integer.config_externalDisplayPeakHeight);
|
||||
mRefreshRateSynchronizationEnabled = getResources(context).getBoolean(
|
||||
com.android.internal.R.bool.config_refreshRateSynchronizationEnabled);
|
||||
Log.d(TAG, "mExternalDisplayPeakRefreshRate=" + mExternalDisplayPeakRefreshRate);
|
||||
Log.d(TAG, "mExternalDisplayPeakWidth=" + mExternalDisplayPeakWidth);
|
||||
Log.d(TAG, "mExternalDisplayPeakHeight=" + mExternalDisplayPeakHeight);
|
||||
Log.d(TAG, "mRefreshRateSynchronizationEnabled=" + mRefreshRateSynchronizationEnabled);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user