diff --git a/devicesettings/Android.mk b/devicesettings/Android.mk
new file mode 100644
index 0000000..a01029b
--- /dev/null
+++ b/devicesettings/Android.mk
@@ -0,0 +1,38 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_PACKAGE_NAME := DeviceSettings
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ androidx.core_core \
+ androidx.preference_preference \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ org.lineageos.platform.internal \
+ vendor.nvidia.hardware.graphics.display-V1.0-java
+
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/res \
+ $(TOP)/packages/resources/devicesettings/res
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_REQUIRED_MODULES := \
+ privapp-permissions-lineage-displaysettings.xml
+
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := privapp-permissions-lineage-displaysettings.xml
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := permissions/privapp-permissions-lineage-displaysettings.xml
+LOCAL_MODULE_RELATIVE_PATH := permissions
+include $(BUILD_PREBUILT)
diff --git a/devicesettings/AndroidManifest.xml b/devicesettings/AndroidManifest.xml
new file mode 100644
index 0000000..3035062
--- /dev/null
+++ b/devicesettings/AndroidManifest.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devicesettings/permissions/privapp-permissions-lineage-displaysettings.xml b/devicesettings/permissions/privapp-permissions-lineage-displaysettings.xml
new file mode 100644
index 0000000..621b289
--- /dev/null
+++ b/devicesettings/permissions/privapp-permissions-lineage-displaysettings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/devicesettings/proguard.flags b/devicesettings/proguard.flags
new file mode 100644
index 0000000..43b57d9
--- /dev/null
+++ b/devicesettings/proguard.flags
@@ -0,0 +1,3 @@
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
diff --git a/devicesettings/res/drawable/ic_settings_additional_buttons.xml b/devicesettings/res/drawable/ic_settings_additional_buttons.xml
new file mode 100644
index 0000000..402839f
--- /dev/null
+++ b/devicesettings/res/drawable/ic_settings_additional_buttons.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/devicesettings/res/mipmap-hdpi/ic_launcher.png b/devicesettings/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/devicesettings/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/devicesettings/res/mipmap-mdpi/ic_launcher.png b/devicesettings/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/devicesettings/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/devicesettings/res/mipmap-xhdpi/ic_launcher.png b/devicesettings/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/devicesettings/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/devicesettings/res/mipmap-xxhdpi/ic_launcher.png b/devicesettings/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/devicesettings/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/devicesettings/res/mipmap-xxhdpi/ic_launcher_settings.png b/devicesettings/res/mipmap-xxhdpi/ic_launcher_settings.png
new file mode 100644
index 0000000..3fa69e9
Binary files /dev/null and b/devicesettings/res/mipmap-xxhdpi/ic_launcher_settings.png differ
diff --git a/devicesettings/res/mipmap-xxxhdpi/ic_launcher.png b/devicesettings/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/devicesettings/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/devicesettings/res/values/integers.xml b/devicesettings/res/values/integers.xml
new file mode 100644
index 0000000..2dd4238
--- /dev/null
+++ b/devicesettings/res/values/integers.xml
@@ -0,0 +1,19 @@
+
+
+
+ 15
+
\ No newline at end of file
diff --git a/devicesettings/res/values/strings.xml b/devicesettings/res/values/strings.xml
new file mode 100644
index 0000000..52751e4
--- /dev/null
+++ b/devicesettings/res/values/strings.xml
@@ -0,0 +1,26 @@
+
+
+
+ Display Configuration
+ Adjust external display and dock settings
+ Select Mode
+ Confirmation
+ Are you sure you want to keep these display settings?\n\nWill revert in %1$d seconds
+ Turn off when an external display is connected
+ The internal panel will be turned off when an external display is connected
+ Both the internal panel and external display will show the same image
+
diff --git a/devicesettings/res/values/styles.xml b/devicesettings/res/values/styles.xml
new file mode 100644
index 0000000..aec8fce
--- /dev/null
+++ b/devicesettings/res/values/styles.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devicesettings/res/xml/display_panel.xml b/devicesettings/res/xml/display_panel.xml
new file mode 100644
index 0000000..53ae4a3
--- /dev/null
+++ b/devicesettings/res/xml/display_panel.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/devicesettings/src/org/lineageos/settings/device/BootCompletedReceiver.java b/devicesettings/src/org/lineageos/settings/device/BootCompletedReceiver.java
new file mode 100644
index 0000000..72d1314
--- /dev/null
+++ b/devicesettings/src/org/lineageos/settings/device/BootCompletedReceiver.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 The LineageOS 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 org.lineageos.settings.device;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class BootCompletedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ context.startService(new Intent(context, DisplayService.class));
+ }
+}
\ No newline at end of file
diff --git a/devicesettings/src/org/lineageos/settings/device/DeviceSettingsSearchIndexablesProvider.java b/devicesettings/src/org/lineageos/settings/device/DeviceSettingsSearchIndexablesProvider.java
new file mode 100644
index 0000000..968fe8e
--- /dev/null
+++ b/devicesettings/src/org/lineageos/settings/device/DeviceSettingsSearchIndexablesProvider.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The LineageOS 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 org.lineageos.settings.device;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.SearchIndexableResource;
+import android.provider.SearchIndexablesProvider;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
+import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
+import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
+import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
+
+public class DeviceSettingsSearchIndexablesProvider extends SearchIndexablesProvider {
+ public static final int SEARCH_IDX_BUTTON_PANEL = 0;
+ private static final String TAG = "ConfigPanelSearchIndexablesProvider";
+ private static SearchIndexableResource[] INDEXABLE_RES = new SearchIndexableResource[]{
+ new SearchIndexableResource(1, R.xml.display_panel,
+ DisplaySettingsActivity.class.getName(),
+ R.drawable.ic_settings_additional_buttons),
+ };
+
+ private static Object[] generateResourceRef(SearchIndexableResource sir) {
+ Object[] ref = new Object[7];
+ ref[COLUMN_INDEX_XML_RES_RANK] = sir.rank;
+ ref[COLUMN_INDEX_XML_RES_RESID] = sir.xmlResId;
+ ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = null;
+ ref[COLUMN_INDEX_XML_RES_ICON_RESID] = sir.iconResId;
+ ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = "com.android.settings.action.EXTRA_SETTINGS";
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = "org.lineageos.settings.device";
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = sir.className;
+ return ref;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor queryXmlResources(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
+ cursor.addRow(generateResourceRef(INDEXABLE_RES[SEARCH_IDX_BUTTON_PANEL]));
+ return cursor;
+ }
+
+ @Override
+ public Cursor queryRawData(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
+ return cursor;
+ }
+
+ @Override
+ public Cursor queryNonIndexableKeys(String[] projection) {
+ MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
+ return cursor;
+ }
+}
diff --git a/devicesettings/src/org/lineageos/settings/device/DisplayService.java b/devicesettings/src/org/lineageos/settings/device/DisplayService.java
new file mode 100644
index 0000000..7d65d7b
--- /dev/null
+++ b/devicesettings/src/org/lineageos/settings/device/DisplayService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The LineageOS 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 org.lineageos.settings.device;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.WindowManagerPolicyConstants;
+
+public class DisplayService extends Service {
+ private static final String TAG = DisplayService.class.getSimpleName();
+ final Receiver mReceiver = new Receiver();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // We don't provide binding, so return null
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ mReceiver.init();
+
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ final class Receiver extends BroadcastReceiver {
+ private boolean mExternalDisplayConnected = false;
+
+ public void init() {
+ IntentFilter filter = new IntentFilter();
+
+ filter.addAction(WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+
+ registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SharedPreferences sharedPrefs = context.getSharedPreferences("org.lineageos.settings.device_preferences", context.MODE_PRIVATE);
+ Log.w(TAG, intent.getAction());
+ String action = intent.getAction();
+
+ switch (action) {
+ case WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED:
+ mExternalDisplayConnected = intent.getBooleanExtra(WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE, false);
+ case Intent.ACTION_SCREEN_ON:
+ DisplayUtils.setInternalDisplayState(!(mExternalDisplayConnected && sharedPrefs.getBoolean("disable_internal_on_external_connected", false)));
+ default:
+ break;
+ }
+ }
+ }
+
+ ;
+}
diff --git a/devicesettings/src/org/lineageos/settings/device/DisplaySettingsActivity.java b/devicesettings/src/org/lineageos/settings/device/DisplaySettingsActivity.java
new file mode 100644
index 0000000..793d3b3
--- /dev/null
+++ b/devicesettings/src/org/lineageos/settings/device/DisplaySettingsActivity.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The LineageOS 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 org.lineageos.settings.device;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.WindowManagerPolicyConstants;
+
+
+public class DisplaySettingsActivity extends PreferenceActivity {
+ public final Receiver mReceiver = new Receiver();
+ private DisplaySettingsFragment mFragment;
+ public boolean mExternalDisplayConnected;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mFragment = new DisplaySettingsFragment();
+
+ getFragmentManager().beginTransaction().replace(android.R.id.content, mFragment).commit();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mReceiver);
+ }
+
+ protected void onResume() {
+ super.onResume();
+ mReceiver.init(this);
+ }
+
+ final class Receiver extends BroadcastReceiver {
+ private boolean mReceivedInitialIntent = false;
+ public boolean mBlocked;
+
+ public void init(Context context) {
+ // Refresh UI on external display status change
+ IntentFilter filter = new IntentFilter();
+
+ filter.addAction(WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED);
+
+ mReceivedInitialIntent = false;
+ context.registerReceiver(this, filter);
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ mExternalDisplayConnected = intent.getBooleanExtra(WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE, false);
+
+ if (mBlocked)
+ return;
+
+ if (mReceivedInitialIntent)
+ recreate();
+
+ mReceivedInitialIntent = true;
+ }
+ }
+
+ ;
+}
diff --git a/devicesettings/src/org/lineageos/settings/device/DisplaySettingsFragment.java b/devicesettings/src/org/lineageos/settings/device/DisplaySettingsFragment.java
new file mode 100644
index 0000000..0c6c55b
--- /dev/null
+++ b/devicesettings/src/org/lineageos/settings/device/DisplaySettingsFragment.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2020 The LineageOS 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 org.lineageos.settings.device;
+
+import android.app.ActionBar;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.MenuItem;
+
+import android.app.AlertDialog;
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcDisplay;
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcDisplayMode;
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcDisplayType;
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcModeType;
+import vendor.nvidia.hardware.graphics.display.V1_0.INvDisplay;
+
+public class DisplaySettingsFragment extends PreferenceFragment
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private static final String TAG = DisplaySettingsFragment.class.getSimpleName();
+ public boolean mInModeChange = false;
+ private INvDisplay mDisplayService;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ try {
+ mDisplayService = INvDisplay.getService(true /* retry */);
+ } catch (RemoteException e) {
+ }
+
+ addPreferencesFromResource(R.xml.display_panel);
+ PreferenceScreen preferenceScreen = this.getPreferenceScreen();
+
+ for (int i = HwcSvcDisplay.HWC_SVC_DISPLAY_PANEL; i <= HwcSvcDisplay.HWC_SVC_DISPLAY_HDMI2; i++) {
+ PreferenceCategory category = new PreferenceCategory(preferenceScreen.getContext());
+
+ if (!initializeDisplayCategory(category, i))
+ continue;
+
+ preferenceScreen.addPreference(category);
+ populateDisplayCategory(category, i);
+
+ }
+
+ final ActionBar actionBar = getActivity().getActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ prefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ getActivity().onBackPressed();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPrefs, String key) {
+ HashMap uidMap = DisplayUtils.makeUidMap(mDisplayService);
+ if (key.startsWith("mode_")) {
+ String hash = key.substring(5);
+ int display = uidMap.getOrDefault(hash, -1);
+
+ if (display >= 0) {
+ int modeIndex = Integer.valueOf(sharedPrefs.getString(key, ""));
+ ((DisplaySettingsActivity)getActivity()).mReceiver.mBlocked = true;
+ performModeChange(sharedPrefs, key, modeIndex, display);
+ }
+
+ return;
+ }
+
+ if (key.equals("disable_internal_on_external_connected")) {
+ DisplayUtils.setInternalDisplayState(!(((DisplaySettingsActivity)getActivity()).mExternalDisplayConnected && sharedPrefs.getBoolean(key, false)));
+ return;
+ }
+ }
+
+ private void performModeChange(SharedPreferences sharedPrefs, String key, int modeIndex, int display) {
+ try {
+ mDisplayService.modeDefaultSetIndex(display, modeIndex);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set default display mode");
+ return;
+ }
+
+ DialogInterface.OnClickListener confirmationDialogClickListener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ try {
+ mDisplayService.modeDefaultCommit(display);
+ mDisplayService.modeDefaultStore(display);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to save default display mode");;
+ }
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ try {
+ mDisplayService.modeDefaultRollback(display);
+ mDisplayService.modeUpdate(display);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to rollback display mode");
+ }
+ break;
+ }
+
+ HwcSvcDisplayMode currentMode;
+
+ try {
+ currentMode = mDisplayService.getMode(display, HwcSvcModeType.HWC_SVC_MODE_TYPE_CURRENT);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to read display mode");
+ ((DisplaySettingsActivity)getActivity()).mReceiver.mBlocked = false;
+ getActivity().recreate();
+ return;
+ }
+
+ SharedPreferences.Editor editor = sharedPrefs.edit();
+ editor.putString(key, String.valueOf(currentMode.index));
+ editor.commit();
+ ((DisplaySettingsActivity)getActivity()).mReceiver.mBlocked = false;
+ getActivity().recreate();
+ }
+ };
+
+ int waitTime = getResources().getInteger(R.integer.mode_confirmation_wait_time);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getView().getContext());
+ builder.setTitle(R.string.mode_confirmation_title)
+ .setPositiveButton(android.R.string.ok, confirmationDialogClickListener)
+ .setNegativeButton(android.R.string.cancel, confirmationDialogClickListener)
+ .setMessage(getString(R.string.mode_confirmation_summary, waitTime));
+
+ AlertDialog confirmationDialog = builder.create();
+ confirmationDialog.show();
+
+ new CountDownTimer(waitTime * 1000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ if (confirmationDialog != null && isAdded())
+ confirmationDialog.setMessage(getString(R.string.mode_confirmation_summary, millisUntilFinished / 1000));
+ }
+
+ @Override
+ public void onFinish() {
+ if (confirmationDialog != null && isAdded())
+ confirmationDialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+ }
+ }.start();
+ }
+
+ private boolean initializeDisplayCategory(PreferenceCategory category, int display) {
+ try {
+ int type = mDisplayService.displayGetType(display);
+ ArrayList availableModes = mDisplayService.modeGetList(display);
+
+ if (availableModes.size() == 0)
+ return false; // Display is not connected
+
+ category.setTitle(DisplayUtils.makeDisplayLabel(mDisplayService.edidGetInfo(display), display));
+ category.setSummary(HwcSvcDisplayType.toString(type));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to read display info");
+ return false;
+ }
+
+ return true;
+ }
+
+ private void populateDisplayCategory(PreferenceCategory category, int display) {
+ String displayUid;
+ HwcSvcDisplayMode currentMode;
+ ArrayList availableModes;
+
+ try {
+ displayUid = String.valueOf(DisplayUtils.makeDisplayLabel(mDisplayService.edidGetInfo(display), display).hashCode());
+ currentMode = mDisplayService.getMode(display, HwcSvcModeType.HWC_SVC_MODE_TYPE_CURRENT);
+ availableModes = mDisplayService.modeGetList(display);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to read display info");
+ return;
+ }
+
+ // Sort by resolution and refresh rate
+ Collections.sort(availableModes, new Comparator() {
+ public int compare(Object a, Object b) {
+ HwcSvcDisplayMode modeA = (HwcSvcDisplayMode) a;
+ HwcSvcDisplayMode modeB = (HwcSvcDisplayMode) b;
+
+ if (modeA.xres == modeB.xres) {
+ if (modeA.yres == modeB.yres) {
+ if (modeA.refresh == modeB.refresh)
+ return 0;
+ else if (modeA.refresh > modeB.refresh)
+ return -1;
+ else
+ return 1;
+ } else if (modeA.yres > modeB.yres) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else if (modeA.xres > modeB.xres) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ });
+
+ ListPreference modesPreference = new ListPreference(category.getContext());
+ ArrayList displayedModes = new ArrayList();
+ ArrayList modeIndices = new ArrayList();
+
+ availableModes.forEach((mode) -> displayedModes.add(DisplayUtils.makeModeInfoString(mode) + " " + DisplayUtils.makeColorInfoString(mode)));
+ availableModes.forEach((mode) -> modeIndices.add(String.valueOf(mode.index)));
+
+ modesPreference.setEntries(displayedModes.toArray(new CharSequence[displayedModes.size()]));
+ modesPreference.setEntryValues(modeIndices.toArray(new CharSequence[modeIndices.size()]));
+ modesPreference.setTitle(R.string.mode_selection_title);
+ modesPreference.setSummary(DisplayUtils.makeModeInfoString(currentMode) + "\n" + DisplayUtils.makeColorInfoString(currentMode));
+ modesPreference.setKey("mode_" + displayUid);
+ modesPreference.setValue(String.valueOf(currentMode.index));
+
+ category.addPreference(modesPreference);
+
+ // Show checkbox to disable internal panel when an external display is connected
+ if (display == HwcSvcDisplay.HWC_SVC_DISPLAY_PANEL) {
+ SwitchPreference disableInternalOnExternalConnectedPreference = new SwitchPreference(category.getContext());
+ disableInternalOnExternalConnectedPreference.setTitle(R.string.disable_internal_on_external_connected_title);
+ disableInternalOnExternalConnectedPreference.setSummaryOn(R.string.disable_internal_on_external_connected_summary_on);
+ disableInternalOnExternalConnectedPreference.setSummaryOff(R.string.disable_internal_on_external_connected_summary_off);
+ disableInternalOnExternalConnectedPreference.setKey("disable_internal_on_external_connected");
+
+ category.addPreference(disableInternalOnExternalConnectedPreference);
+ }
+ }
+}
diff --git a/devicesettings/src/org/lineageos/settings/device/DisplayUtils.java b/devicesettings/src/org/lineageos/settings/device/DisplayUtils.java
new file mode 100644
index 0000000..5cb2f59
--- /dev/null
+++ b/devicesettings/src/org/lineageos/settings/device/DisplayUtils.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 The LineageOS 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 org.lineageos.settings.device;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcDisplay;
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcDisplayMode;
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcDisplayModePixEnc;
+import vendor.nvidia.hardware.graphics.display.V1_0.HwcSvcEdidInfo;
+import vendor.nvidia.hardware.graphics.display.V1_0.INvDisplay;
+
+public class DisplayUtils {
+ private static final String TAG = DisplayUtils.class.getSimpleName();
+
+ public static void setInternalDisplayState(boolean state) {
+ Log.d(TAG, "setInternalDisplayState: " + String.valueOf(state));
+ try {
+ FileOutputStream enableFile = new FileOutputStream("/sys/bus/platform/devices/tegradc.0/enable");
+ byte[] buf = new byte[2];
+
+ buf[0] = (byte) (state ? '1' : '0');
+ buf[1] = '\n';
+
+ enableFile.write(buf);
+ enableFile.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display state");
+ }
+ }
+
+ public static HashMap makeUidMap(INvDisplay displayService) {
+ HashMap uidMap = new HashMap();
+ for (int i = HwcSvcDisplay.HWC_SVC_DISPLAY_PANEL; i <= HwcSvcDisplay.HWC_SVC_DISPLAY_HDMI2; i++) {
+ try {
+ String displayUid = String.valueOf(makeDisplayLabel(displayService.edidGetInfo(i), i).hashCode()); // A unique id we can use to identify the display
+ uidMap.put(displayUid, i);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+
+ return uidMap;
+ }
+
+ public static String makeDisplayLabel(HwcSvcEdidInfo edid, int display) {
+ if (!edid.monitor_name.isEmpty())
+ return (edid.manufacturer_id.isEmpty() ? "" : (edid.manufacturer_id + " - ")) + edid.monitor_name; // Report name from edid if present
+ if (display == HwcSvcDisplay.HWC_SVC_DISPLAY_PANEL)
+ return "Internal Panel"; // Most internal panels do not have an edid
+
+ return "Unknown";
+ }
+
+ public static String makeModeInfoString(HwcSvcDisplayMode mode) {
+ return String.format("%dx%d %sHz", mode.xres, mode.yres, new DecimalFormat("###.##").format(mode.refresh));
+ }
+
+ public static String makeColorInfoString(HwcSvcDisplayMode mode) {
+ String encodingStr = "";
+ String colorimetryStr = "";
+
+ switch (mode.pixenc) {
+ case HwcSvcDisplayModePixEnc.HWC_SVC_DISP_PIXENC_YUV444:
+ encodingStr = "YUV444";
+ break;
+ case HwcSvcDisplayModePixEnc.HWC_SVC_DISP_PIXENC_YUV422:
+ encodingStr = "YUV422";
+ break;
+ case HwcSvcDisplayModePixEnc.HWC_SVC_DISP_PIXENC_YUV420:
+ encodingStr = "YUV420";
+ break;
+ default:
+ encodingStr = "RGB";
+ break;
+ }
+
+ if (mode.colorimetry != 1)
+ colorimetryStr = "Rec. 709";
+ else
+ colorimetryStr = "Rec. 2020";
+
+ return String.format("%s %d-bit %s", encodingStr, mode.bpc, colorimetryStr);
+ }
+}
\ No newline at end of file