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.Nullable; 27 import androidx.annotation.VisibleForTesting; 28 import androidx.preference.Preference; 29 import androidx.preference.TwoStatePreference; 30 31 import com.android.settings.R; 32 import com.android.settings.core.PreferenceControllerMixin; 33 import com.android.settings.development.DevelopmentSettingsDashboardFragment; 34 import com.android.settings.development.RebootConfirmationDialogFragment; 35 import com.android.settings.development.RebootConfirmationDialogHost; 36 import com.android.settingslib.development.DeveloperOptionsPreferenceController; 37 38 /** Controller to handle the events when user toggles this developer option switch: Enable ANGLE */ 39 public class GraphicsDriverEnableAngleAsSystemDriverController 40 extends DeveloperOptionsPreferenceController 41 implements Preference.OnPreferenceChangeListener, 42 PreferenceControllerMixin, 43 RebootConfirmationDialogHost { 44 45 private static final String TAG = "GraphicsEnableAngleCtrl"; 46 47 private static final String ENABLE_ANELE_AS_SYSTEM_DRIVER_KEY = "enable_angle_as_system_driver"; 48 49 @Nullable private final DevelopmentSettingsDashboardFragment mFragment; 50 51 private final GraphicsDriverSystemPropertiesWrapper mSystemProperties; 52 53 private boolean mShouldToggleSwitchBackOnRebootDialogDismiss; 54 55 @VisibleForTesting 56 static final String PROPERTY_PERSISTENT_GRAPHICS_EGL = "persist.graphics.egl"; 57 58 @VisibleForTesting 59 static final String PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION = 60 "debug.graphics.angle.developeroption.enable"; 61 62 @VisibleForTesting static final String ANGLE_DRIVER_SUFFIX = "angle"; 63 64 @VisibleForTesting 65 static class Injector { createSystemPropertiesWrapper()66 public GraphicsDriverSystemPropertiesWrapper createSystemPropertiesWrapper() { 67 return new GraphicsDriverSystemPropertiesWrapper() { 68 @Override 69 public String get(String key, String def) { 70 return SystemProperties.get(key, def); 71 } 72 73 @Override 74 public void set(String key, String val) { 75 SystemProperties.set(key, val); 76 } 77 78 @Override 79 public boolean getBoolean(String key, boolean def) { 80 return SystemProperties.getBoolean(key, def); 81 } 82 }; 83 } 84 } 85 86 public GraphicsDriverEnableAngleAsSystemDriverController( 87 Context context, @Nullable DevelopmentSettingsDashboardFragment fragment) { 88 this(context, fragment, new Injector()); 89 } 90 91 // Return true if the ANGLE developer option entry point is enabled. 92 // This can be enabled by calling: 93 // `adb shell setprop debug.graphics.angle.developeroption.enable true` 94 private boolean isAngleDeveloperOptionEnabled() { 95 return mSystemProperties.getBoolean(PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION, false); 96 } 97 98 @VisibleForTesting 99 GraphicsDriverEnableAngleAsSystemDriverController( 100 Context context, @Nullable DevelopmentSettingsDashboardFragment fragment, Injector injector) { 101 super(context); 102 mFragment = fragment; 103 mSystemProperties = injector.createSystemPropertiesWrapper(); 104 // By default, when the reboot dialog is dismissed we want to toggle the switch back. 105 // Exception is when user chooses to reboot now, the switch should keep its current value 106 // and persist its' state over reboot. 107 mShouldToggleSwitchBackOnRebootDialogDismiss = true; 108 final String persistGraphicsEglValue = 109 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 110 Log.v(TAG, "Value of " + PROPERTY_PERSISTENT_GRAPHICS_EGL + " is: " 111 + persistGraphicsEglValue); 112 } 113 114 @Override 115 public String getPreferenceKey() { 116 return ENABLE_ANELE_AS_SYSTEM_DRIVER_KEY; 117 } 118 119 @Override 120 public boolean onPreferenceChange(Preference preference, Object newValue) { 121 final boolean enableAngleAsSystemDriver = (Boolean) newValue; 122 // set "persist.graphics.egl" to "angle" if enableAngleAsSystemDriver is true 123 // set "persist.graphics.egl" to "" if enableAngleAsSystemDriver is false 124 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(enableAngleAsSystemDriver); 125 // pop up a window asking user to reboot to make the new "persist.graphics.egl" take effect 126 showRebootDialog(); 127 return true; 128 } 129 130 @VisibleForTesting 131 void showRebootDialog() { 132 RebootConfirmationDialogFragment.show( 133 mFragment, 134 R.string.reboot_dialog_enable_angle_as_system_driver, 135 R.string.cancel, 136 this); 137 } 138 139 /** Return the default value of "persist.graphics.egl" */ 140 public boolean isDefaultValue() { 141 final String currentGlesDriver = 142 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 143 // default value of "persist.graphics.egl" is "" 144 return TextUtils.isEmpty(currentGlesDriver); 145 } 146 147 @Override 148 public void updateState(Preference preference) { 149 super.updateState(preference); 150 // set switch on if "persist.graphics.egl" is "angle". 151 final String currentGlesDriver = 152 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 153 final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver); 154 ((TwoStatePreference) mPreference).setChecked(isAngle); 155 156 // Disable the developer option toggle UI if ANGLE is disabled, this means next time the 157 // debug property needs to be set to true again to enable ANGLE. If ANGLE is enabled, don't 158 // disable the developer option toggle UI so that it can be turned off easily. 159 if (!isAngleDeveloperOptionEnabled() && !((TwoStatePreference) mPreference).isChecked()) { 160 mPreference.setEnabled(false); 161 } 162 } 163 164 @Override 165 protected void onDeveloperOptionsSwitchDisabled() { 166 // 1) disable the switch 167 super.onDeveloperOptionsSwitchDisabled(); 168 // 2) set the persist.graphics.egl empty string 169 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); 170 // 3) reset the switch 171 ((TwoStatePreference) mPreference).setChecked(false); 172 } 173 174 void toggleSwitchBack() { 175 final String currentGlesDriver = 176 mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, ""); 177 if (TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver)) { 178 // if persist.graphics.egl = "angle", set the property value back to "" 179 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false); 180 // toggle switch off 181 ((TwoStatePreference) mPreference).setChecked(false); 182 return; 183 } 184 185 if (TextUtils.isEmpty(currentGlesDriver)) { 186 // if persist.graphicx.egl = "", set the persist.graphics.egl back to "angle" 187 GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(true); 188 // toggle switch on 189 ((TwoStatePreference) mPreference).setChecked(true); 190 return; 191 } 192 193 // if persist.graphics.egl holds values other than the above two, log error message 194 Log.e(TAG, "Invalid persist.graphics.egl property value"); 195 } 196 197 @VisibleForTesting 198 void rebootDevice(Context context) { 199 final Intent intent = new Intent(Intent.ACTION_REBOOT).setPackage("android"); 200 context.startActivity(intent); 201 } 202 203 @Override 204 public void onRebootConfirmed(Context context) { 205 // User chooses to reboot now, do not toggle switch back 206 mShouldToggleSwitchBackOnRebootDialogDismiss = false; 207 208 // Reboot the device 209 rebootDevice(context); 210 } 211 212 @Override 213 public void onRebootCancelled() { 214 // User chooses to cancel reboot, toggle switch back 215 mShouldToggleSwitchBackOnRebootDialogDismiss = true; 216 } 217 218 @Override 219 public void onRebootDialogDismissed() { 220 // If reboot dialog is dismissed either from 221 // 1) User clicks cancel 222 // 2) User taps phone screen area outside of reboot dialog 223 // do not reboot the device, and toggles switch back. 224 if (mShouldToggleSwitchBackOnRebootDialogDismiss) { 225 toggleSwitchBack(); 226 } 227 228 // Reset the flag so that the default option is to toggle switch back 229 // on reboot dialog dismissed. 230 mShouldToggleSwitchBackOnRebootDialogDismiss = true; 231 } 232 } 233