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