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