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