1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.development.graphicsdriver; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.os.GraphicsEnvironment; 22 import android.os.SystemProperties; 23 import android.text.TextUtils; 24 import android.util.Log; 25 26 import androidx.annotation.VisibleForTesting; 27 import androidx.preference.Preference; 28 import androidx.preference.SwitchPreference; 29 30 import com.android.settings.R; 31 import com.android.settings.core.PreferenceControllerMixin; 32 import com.android.settings.development.DevelopmentSettingsDashboardFragment; 33 import com.android.settings.development.RebootConfirmationDialogFragment; 34 import com.android.settings.development.RebootConfirmationDialogHost; 35 import com.android.settingslib.development.DeveloperOptionsPreferenceController; 36 37 /** Controller to handle the events when user toggles this developer option switch: Enable ANGLE */ 38 public class GraphicsDriverEnableAngleAsSystemDriverController 39 extends DeveloperOptionsPreferenceController 40 implements Preference.OnPreferenceChangeListener, 41 PreferenceControllerMixin, 42 RebootConfirmationDialogHost { 43 44 private static final String TAG = "GraphicsEnableAngleCtrl"; 45 46 private static final String ENABLE_ANELE_AS_SYSTEM_DRIVER_KEY = "enable_angle_as_system_driver"; 47 48 private final DevelopmentSettingsDashboardFragment mFragment; 49 50 private final GraphicsDriverSystemPropertiesWrapper mSystemProperties; 51 52 private boolean mShouldToggleSwitchBackOnRebootDialogDismiss; 53 54 @VisibleForTesting 55 static final String PROPERTY_RO_GFX_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; 56 57 @VisibleForTesting 58 static final String PROPERTY_PERSISTENT_GRAPHICS_EGL = "persist.graphics.egl"; 59 60 @VisibleForTesting 61 static final String PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION = 62 "debug.graphics.angle.developeroption.enable"; 63 64 @VisibleForTesting static final String ANGLE_DRIVER_SUFFIX = "angle"; 65 66 @VisibleForTesting 67 static class Injector { createSystemPropertiesWrapper()68 public GraphicsDriverSystemPropertiesWrapper createSystemPropertiesWrapper() { 69 return new GraphicsDriverSystemPropertiesWrapper() { 70 @Override 71 public String get(String key, String def) { 72 return SystemProperties.get(key, def); 73 } 74 75 @Override 76 public void set(String key, String val) { 77 SystemProperties.set(key, val); 78 } 79 80 @Override 81 public boolean getBoolean(String key, boolean def) { 82 return SystemProperties.getBoolean(key, def); 83 } 84 }; 85 } 86 } 87 88 public GraphicsDriverEnableAngleAsSystemDriverController( 89 Context context, DevelopmentSettingsDashboardFragment fragment) { 90 this(context, fragment, new Injector()); 91 } 92 93 // Return true if the ANGLE developer option entry point is enabled. 94 // This can be enabled by calling: 95 // `adb shell setprop debug.graphics.angle.developeroption.enable true` 96 private boolean isAngleDeveloperOptionEnabled() { 97 return mSystemProperties.getBoolean(PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION, false); 98 } 99 100 private boolean isAngleSupported() { 101 return TextUtils.equals( 102 mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true"); 103 } 104 105 @VisibleForTesting 106 GraphicsDriverEnableAngleAsSystemDriverController( 107 Context context, DevelopmentSettingsDashboardFragment fragment, Injector injector) { 108 super(context); 109 mFragment = fragment; 110 mSystemProperties = injector.createSystemPropertiesWrapper(); 111 // By default, when the reboot dialog is dismissed we want to toggle the switch back. 112 // Exception is when user chooses to reboot now, the switch should keep its current value 113 // and persist its' state over reboot. 114 mShouldToggleSwitchBackOnRebootDialogDismiss = true; 115 final String persistGraphicsEglValue = 116 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 117 Log.v(TAG, "Value of " + PROPERTY_PERSISTENT_GRAPHICS_EGL + " is: " 118 + persistGraphicsEglValue); 119 } 120 121 @Override 122 public String getPreferenceKey() { 123 return ENABLE_ANELE_AS_SYSTEM_DRIVER_KEY; 124 } 125 126 @Override 127 public boolean onPreferenceChange(Preference preference, Object newValue) { 128 final boolean enableAngleAsSystemDriver = (Boolean) newValue; 129 // set "persist.graphics.egl" to "angle" if enableAngleAsSystemDriver is true 130 // set "persist.graphics.egl" to "" if enableAngleAsSystemDriver is false 131 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(enableAngleAsSystemDriver); 132 // pop up a window asking user to reboot to make the new "persist.graphics.egl" take effect 133 showRebootDialog(); 134 return true; 135 } 136 137 @VisibleForTesting 138 void showRebootDialog() { 139 RebootConfirmationDialogFragment.show( 140 mFragment, 141 R.string.reboot_dialog_enable_angle_as_system_driver, 142 R.string.cancel, 143 this); 144 } 145 146 /** Return the default value of "persist.graphics.egl" */ 147 public boolean isDefaultValue() { 148 if (!isAngleSupported()) { 149 return true; 150 } 151 152 final String currentGlesDriver = 153 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 154 // default value of "persist.graphics.egl" is "" 155 return TextUtils.isEmpty(currentGlesDriver); 156 } 157 158 @Override 159 public void updateState(Preference preference) { 160 super.updateState(preference); 161 if (isAngleSupported()) { 162 // set switch on if "persist.graphics.egl" is "angle" and angle is built in /vendor 163 // set switch off otherwise. 164 final String currentGlesDriver = 165 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 166 final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver); 167 ((SwitchPreference) mPreference).setChecked(isAngle); 168 } else { 169 mPreference.setEnabled(false); 170 ((SwitchPreference) mPreference).setChecked(false); 171 } 172 173 // Regardless of whether ANGLE is enabled, disable the developer option UI 174 // as long as UI is not enabled via debug property. 175 if (!isAngleDeveloperOptionEnabled()) { 176 mPreference.setEnabled(false); 177 } 178 } 179 180 @Override 181 protected void onDeveloperOptionsSwitchDisabled() { 182 // 1) disable the switch 183 super.onDeveloperOptionsSwitchDisabled(); 184 if (isAngleSupported()) { 185 // 2) set the persist.graphics.egl empty string 186 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); 187 // 3) reset the switch 188 ((SwitchPreference) mPreference).setChecked(false); 189 } 190 } 191 192 void toggleSwitchBack() { 193 final String currentGlesDriver = 194 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 195 if (TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver)) { 196 // if persist.graphics.egl = "angle", set the property value back to "" 197 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); 198 // toggle switch off 199 ((SwitchPreference) mPreference).setChecked(false); 200 return; 201 } 202 203 if (TextUtils.isEmpty(currentGlesDriver)) { 204 // if persist.graphicx.egl = "", set the persist.graphics.egl back to "angle" 205 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(true); 206 // toggle switch on 207 ((SwitchPreference) mPreference).setChecked(true); 208 return; 209 } 210 211 // if persist.graphics.egl holds values other than the above two, log error message 212 Log.e(TAG, "Invalid persist.graphics.egl property value"); 213 } 214 215 @VisibleForTesting 216 void rebootDevice(Context context) { 217 final Intent intent = new Intent(Intent.ACTION_REBOOT); 218 context.startActivity(intent); 219 } 220 221 @Override 222 public void onRebootConfirmed(Context context) { 223 // User chooses to reboot now, do not toggle switch back 224 mShouldToggleSwitchBackOnRebootDialogDismiss = false; 225 226 // Reboot the device 227 rebootDevice(context); 228 } 229 230 @Override 231 public void onRebootCancelled() { 232 // User chooses to cancel reboot, toggle switch back 233 mShouldToggleSwitchBackOnRebootDialogDismiss = true; 234 } 235 236 @Override 237 public void onRebootDialogDismissed() { 238 // If reboot dialog is dismissed either from 239 // 1) User clicks cancel 240 // 2) User taps phone screen area outside of reboot dialog 241 // do not reboot the device, and toggles switch back. 242 if (mShouldToggleSwitchBackOnRebootDialogDismiss) { 243 toggleSwitchBack(); 244 } 245 246 // Reset the flag so that the default option is to toggle switch back 247 // on reboot dialog dismissed. 248 mShouldToggleSwitchBackOnRebootDialogDismiss = true; 249 } 250 } 251