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