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.server.accessibility.magnification;
18 
19 import android.annotation.NonNull;
20 import android.os.Binder;
21 import android.provider.DeviceConfig;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.util.concurrent.Executor;
26 import java.util.concurrent.atomic.AtomicBoolean;
27 
28 /**
29  * Abstract base class to encapsulates the feature flags for magnification features.
30  * {@see DeviceConfig}
31  *
32  * @hide
33  */
34 abstract class MagnificationFeatureFlagBase {
35 
getNamespace()36     abstract String getNamespace();
getFeatureName()37     abstract String getFeatureName();
getDefaultValue()38     abstract boolean getDefaultValue();
39 
clearCallingIdentifyAndTryCatch(Runnable tryBlock, Runnable catchBlock)40     private void clearCallingIdentifyAndTryCatch(Runnable tryBlock, Runnable catchBlock) {
41         try {
42             Binder.withCleanCallingIdentity(() -> tryBlock.run());
43         } catch (Throwable throwable) {
44             catchBlock.run();
45         }
46     }
47 
48     /** Returns true iff the feature flag is readable and enabled */
isFeatureFlagEnabled()49     public boolean isFeatureFlagEnabled() {
50         AtomicBoolean isEnabled = new AtomicBoolean(getDefaultValue());
51 
52         clearCallingIdentifyAndTryCatch(
53                 () -> isEnabled.set(DeviceConfig.getBoolean(
54                         getNamespace(),
55                         getFeatureName(),
56                         getDefaultValue())),
57                 () -> isEnabled.set(getDefaultValue()));
58 
59         return isEnabled.get();
60     }
61 
62     /** Sets the feature flag. Only used for testing; requires shell permissions. */
63     @VisibleForTesting
setFeatureFlagEnabled(boolean isEnabled)64     public boolean setFeatureFlagEnabled(boolean isEnabled) {
65         AtomicBoolean success = new AtomicBoolean(getDefaultValue());
66 
67         clearCallingIdentifyAndTryCatch(
68                 () -> success.set(DeviceConfig.setProperty(
69                         getNamespace(),
70                         getFeatureName(),
71                         Boolean.toString(isEnabled),
72                         /* makeDefault= */ false)),
73                 () -> success.set(getDefaultValue()));
74 
75         return success.get();
76     }
77 
78     /**
79      * Adds a listener for when the feature flag changes.
80      *
81      * <p>{@see DeviceConfig#addOnPropertiesChangedListener(
82      * String, Executor, DeviceConfig.OnPropertiesChangedListener)}
83      *
84      * <p>Note: be weary of using a DIRECT_EXECUTOR here. You may run into deadlocks! (see
85      * b/281132229)
86      */
87     @NonNull
addOnChangedListener( @onNull Executor executor, @NonNull Runnable listener)88     public DeviceConfig.OnPropertiesChangedListener addOnChangedListener(
89             @NonNull Executor executor, @NonNull Runnable listener) {
90         DeviceConfig.OnPropertiesChangedListener onChangedListener =
91                 properties -> {
92                     if (properties.getKeyset().contains(
93                             getFeatureName())) {
94                         listener.run();
95                     }
96                 };
97 
98         clearCallingIdentifyAndTryCatch(
99                 () -> DeviceConfig.addOnPropertiesChangedListener(
100                         getNamespace(),
101                         executor,
102                         onChangedListener),
103                 () -> {});
104 
105         return onChangedListener;
106     }
107 
108     /**
109      * Remove a listener for when the feature flag changes.
110      *
111      * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor,
112      * DeviceConfig.OnPropertiesChangedListener)}
113      */
removeOnChangedListener( @onNull DeviceConfig.OnPropertiesChangedListener onChangedListener)114     public void removeOnChangedListener(
115             @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) {
116         DeviceConfig.removeOnPropertiesChangedListener(onChangedListener);
117     }
118 }
119