1 /*
2  * Copyright (C) 2018 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.systemui.statusbar.policy;
18 
19 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
20 
21 import android.app.RemoteInput;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.os.Handler;
25 import android.provider.DeviceConfig;
26 import android.text.TextUtils;
27 import android.util.KeyValueListParser;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
32 import com.android.systemui.R;
33 
34 import javax.inject.Inject;
35 import javax.inject.Named;
36 import javax.inject.Singleton;
37 
38 @Singleton
39 public final class SmartReplyConstants {
40 
41     private static final String TAG = "SmartReplyConstants";
42 
43     private final boolean mDefaultEnabled;
44     private final boolean mDefaultRequiresP;
45     private final int mDefaultMaxSqueezeRemeasureAttempts;
46     private final boolean mDefaultEditChoicesBeforeSending;
47     private final boolean mDefaultShowInHeadsUp;
48     private final int mDefaultMinNumSystemGeneratedReplies;
49     private final int mDefaultMaxNumActions;
50     private final int mDefaultOnClickInitDelay;
51 
52     // These fields are updated on the UI thread but can be accessed on both the UI thread and
53     // background threads. We use the volatile keyword here instead of synchronization blocks since
54     // we only care about variable updates here being visible to other threads (and not for example
55     // whether the variables we are reading were updated in the same go).
56     private volatile boolean mEnabled;
57     private volatile boolean mRequiresTargetingP;
58     private volatile int mMaxSqueezeRemeasureAttempts;
59     private volatile boolean mEditChoicesBeforeSending;
60     private volatile boolean mShowInHeadsUp;
61     private volatile int mMinNumSystemGeneratedReplies;
62     private volatile int mMaxNumActions;
63     private volatile long mOnClickInitDelay;
64 
65     private final Handler mHandler;
66     private final Context mContext;
67     private final KeyValueListParser mParser = new KeyValueListParser(',');
68 
69     @Inject
SmartReplyConstants(@amedMAIN_HANDLER_NAME) Handler handler, Context context)70     public SmartReplyConstants(@Named(MAIN_HANDLER_NAME) Handler handler, Context context) {
71         mHandler = handler;
72         mContext = context;
73         final Resources resources = mContext.getResources();
74         mDefaultEnabled = resources.getBoolean(
75                 R.bool.config_smart_replies_in_notifications_enabled);
76         mDefaultRequiresP = resources.getBoolean(
77                 R.bool.config_smart_replies_in_notifications_requires_targeting_p);
78         mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
79                 R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
80         mDefaultEditChoicesBeforeSending = resources.getBoolean(
81                 R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
82         mDefaultShowInHeadsUp = resources.getBoolean(
83                 R.bool.config_smart_replies_in_notifications_show_in_heads_up);
84         mDefaultMinNumSystemGeneratedReplies = resources.getInteger(
85                 R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
86         mDefaultMaxNumActions = resources.getInteger(
87                 R.integer.config_smart_replies_in_notifications_max_num_actions);
88         mDefaultOnClickInitDelay = resources.getInteger(
89                 R.integer.config_smart_replies_in_notifications_onclick_init_delay);
90 
91         registerDeviceConfigListener();
92         updateConstants();
93     }
94 
registerDeviceConfigListener()95     private void registerDeviceConfigListener() {
96         DeviceConfig.addOnPropertiesChangedListener(
97                 DeviceConfig.NAMESPACE_SYSTEMUI,
98                 this::postToHandler,
99                 (properties) -> onDeviceConfigPropertiesChanged(properties.getNamespace()));
100     }
101 
postToHandler(Runnable r)102     private void postToHandler(Runnable r) {
103         this.mHandler.post(r);
104     }
105 
106     @VisibleForTesting
onDeviceConfigPropertiesChanged(String namespace)107     void onDeviceConfigPropertiesChanged(String namespace) {
108         if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(namespace)) {
109             Log.e(TAG, "Received update from DeviceConfig for unrelated namespace: "
110                     + namespace);
111             return;
112         }
113 
114         updateConstants();
115     }
116 
updateConstants()117     private void updateConstants() {
118         synchronized (SmartReplyConstants.this) {
119             mEnabled = readDeviceConfigBooleanOrDefaultIfEmpty(
120                     SystemUiDeviceConfigFlags.SSIN_ENABLED,
121                     mDefaultEnabled);
122             mRequiresTargetingP = readDeviceConfigBooleanOrDefaultIfEmpty(
123                     SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P,
124                     mDefaultRequiresP);
125             mMaxSqueezeRemeasureAttempts = DeviceConfig.getInt(
126                     DeviceConfig.NAMESPACE_SYSTEMUI,
127                     SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS,
128                     mDefaultMaxSqueezeRemeasureAttempts);
129             mEditChoicesBeforeSending = readDeviceConfigBooleanOrDefaultIfEmpty(
130                     SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING,
131                     mDefaultEditChoicesBeforeSending);
132             mShowInHeadsUp = readDeviceConfigBooleanOrDefaultIfEmpty(
133                     SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP,
134                     mDefaultShowInHeadsUp);
135             mMinNumSystemGeneratedReplies = DeviceConfig.getInt(
136                     DeviceConfig.NAMESPACE_SYSTEMUI,
137                     SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES,
138                     mDefaultMinNumSystemGeneratedReplies);
139             mMaxNumActions = DeviceConfig.getInt(
140                     DeviceConfig.NAMESPACE_SYSTEMUI,
141                     SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS,
142                     mDefaultMaxNumActions);
143             mOnClickInitDelay = DeviceConfig.getInt(
144                     DeviceConfig.NAMESPACE_SYSTEMUI,
145                     SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY,
146                     mDefaultOnClickInitDelay);
147         }
148     }
149 
readDeviceConfigBooleanOrDefaultIfEmpty(String propertyName, boolean defaultValue)150     private static boolean readDeviceConfigBooleanOrDefaultIfEmpty(String propertyName,
151             boolean defaultValue) {
152         String value = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI, propertyName);
153         if (TextUtils.isEmpty(value)) {
154             return defaultValue;
155         }
156         if ("true".equals(value)) {
157             return true;
158         }
159         if ("false".equals(value)) {
160             return false;
161         }
162         // For invalid configs we return the default value.
163         return defaultValue;
164     }
165 
166     /** Returns whether smart replies in notifications are enabled. */
isEnabled()167     public boolean isEnabled() {
168         return mEnabled;
169     }
170 
171     /**
172      * Returns whether smart replies in notifications should be disabled when the app targets a
173      * version of Android older than P.
174      */
requiresTargetingP()175     public boolean requiresTargetingP() {
176         return mRequiresTargetingP;
177     }
178 
179     /**
180      * Returns the maximum number of times {@link SmartReplyView#onMeasure(int, int)} will try to
181      * find a better (narrower) line-break for a double-line smart reply button.
182      */
getMaxSqueezeRemeasureAttempts()183     public int getMaxSqueezeRemeasureAttempts() {
184         return mMaxSqueezeRemeasureAttempts;
185     }
186 
187     /**
188      * Returns whether by tapping on a choice should let the user edit the input before it
189      * is sent to the app.
190      *
191      * @param remoteInputEditChoicesBeforeSending The value from
192      *         {@link RemoteInput#getEditChoicesBeforeSending()}
193      */
getEffectiveEditChoicesBeforeSending( @emoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending)194     public boolean getEffectiveEditChoicesBeforeSending(
195             @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) {
196         switch (remoteInputEditChoicesBeforeSending) {
197             case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED:
198                 return false;
199             case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED:
200                 return true;
201             case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO:
202             default:
203                 return mEditChoicesBeforeSending;
204         }
205     }
206 
207     /**
208      * Returns whether smart suggestions should be enabled in heads-up notifications.
209      */
getShowInHeadsUp()210     public boolean getShowInHeadsUp() {
211         return mShowInHeadsUp;
212     }
213 
214     /**
215      * Returns the minimum number of system generated replies to show in a notification.
216      * If we cannot show at least this many system generated replies we should show none.
217      */
getMinNumSystemGeneratedReplies()218     public int getMinNumSystemGeneratedReplies() {
219         return mMinNumSystemGeneratedReplies;
220     }
221 
222     /**
223      * Returns the maximum number smart actions to show in a notification, or -1 if there shouldn't
224      * be a limit.
225      */
getMaxNumActions()226     public int getMaxNumActions() {
227         return mMaxNumActions;
228     }
229 
230     /**
231      * Returns the amount of time (ms) before smart suggestions are clickable, since the suggestions
232      * were added.
233      */
getOnClickInitDelay()234     public long getOnClickInitDelay() {
235         return mOnClickInitDelay;
236     }
237 }
238