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