1 /* 2 * Copyright (C) 2016 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.dialer.voicemail.listui.error; 18 19 import android.content.Context; 20 import android.preference.PreferenceManager; 21 import android.provider.VoicemailContract.Status; 22 import android.support.annotation.Nullable; 23 import android.telecom.PhoneAccountHandle; 24 import com.android.dialer.common.LogUtil; 25 import com.android.dialer.common.PerAccountSharedPreferences; 26 import com.android.dialer.logging.DialerImpression; 27 import com.android.dialer.logging.Logger; 28 import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action; 29 import com.android.voicemail.VoicemailClient; 30 import com.android.voicemail.VoicemailComponent; 31 import java.util.ArrayList; 32 import java.util.List; 33 34 /** 35 * Create error message from {@link VoicemailStatus} for OMTP visual voicemail. This is also the 36 * default behavior if other message creator does not handle the status. 37 */ 38 public class OmtpVoicemailMessageCreator { 39 40 private static final float QUOTA_NEAR_FULL_THRESHOLD = 0.9f; 41 private static final float QUOTA_FULL_THRESHOLD = 0.99f; 42 protected static final String VOICEMAIL_PROMO_DISMISSED_KEY = 43 "voicemail_archive_promo_was_dismissed"; 44 protected static final String VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY = 45 "voicemail_archive_almost_full_promo_was_dismissed"; 46 47 @Nullable create( Context context, VoicemailStatus status, final VoicemailStatusReader statusReader)48 public static VoicemailErrorMessage create( 49 Context context, VoicemailStatus status, final VoicemailStatusReader statusReader) { 50 VoicemailErrorMessage tosMessage = 51 new VoicemailTosMessageCreator(context, status, statusReader).maybeCreateTosMessage(); 52 if (tosMessage != null) { 53 return tosMessage; 54 } 55 56 if (Status.CONFIGURATION_STATE_OK == status.configurationState 57 && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState 58 && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) { 59 return checkQuota(context, status, statusReader); 60 } 61 // Initial state when the source is activating. Other error might be written into data and 62 // notification channel during activation. 63 if (Status.CONFIGURATION_STATE_CONFIGURING == status.configurationState 64 && Status.DATA_CHANNEL_STATE_OK == status.dataChannelState 65 && Status.NOTIFICATION_CHANNEL_STATE_OK == status.notificationChannelState) { 66 return new VoicemailErrorMessage( 67 context.getString(R.string.voicemail_error_activating_title), 68 context.getString(R.string.voicemail_error_activating_message), 69 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle())); 70 } 71 72 if (Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION == status.notificationChannelState) { 73 return createNoSignalMessage(context, status); 74 } 75 76 if (Status.CONFIGURATION_STATE_FAILED == status.configurationState) { 77 return new VoicemailErrorMessage( 78 context.getString(R.string.voicemail_error_activation_failed_title), 79 context.getString(R.string.voicemail_error_activation_failed_message), 80 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 81 VoicemailErrorMessage.createRetryAction(context, status)); 82 } 83 84 if (Status.DATA_CHANNEL_STATE_NO_CONNECTION == status.dataChannelState) { 85 return new VoicemailErrorMessage( 86 context.getString(R.string.voicemail_error_no_data_title), 87 context.getString(R.string.voicemail_error_no_data_message), 88 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 89 VoicemailErrorMessage.createRetryAction(context, status)); 90 } 91 92 if (Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED == status.dataChannelState) { 93 return new VoicemailErrorMessage( 94 context.getString(R.string.voicemail_error_no_data_title), 95 context.getString(R.string.voicemail_error_no_data_cellular_required_message), 96 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 97 VoicemailErrorMessage.createRetryAction(context, status)); 98 } 99 100 if (Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION == status.dataChannelState) { 101 return new VoicemailErrorMessage( 102 context.getString(R.string.voicemail_error_bad_config_title), 103 context.getString(R.string.voicemail_error_bad_config_message), 104 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 105 VoicemailErrorMessage.createRetryAction(context, status)); 106 } 107 108 if (Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR == status.dataChannelState) { 109 return new VoicemailErrorMessage( 110 context.getString(R.string.voicemail_error_communication_title), 111 context.getString(R.string.voicemail_error_communication_message), 112 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 113 VoicemailErrorMessage.createRetryAction(context, status)); 114 } 115 116 if (Status.DATA_CHANNEL_STATE_SERVER_ERROR == status.dataChannelState) { 117 return new VoicemailErrorMessage( 118 context.getString(R.string.voicemail_error_server_title), 119 context.getString(R.string.voicemail_error_server_message), 120 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 121 VoicemailErrorMessage.createRetryAction(context, status)); 122 } 123 124 if (Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR == status.dataChannelState) { 125 return new VoicemailErrorMessage( 126 context.getString(R.string.voicemail_error_server_connection_title), 127 context.getString(R.string.voicemail_error_server_connection_message), 128 VoicemailErrorMessage.createCallVoicemailAction(context, status.getPhoneAccountHandle()), 129 VoicemailErrorMessage.createRetryAction(context, status)); 130 } 131 132 // This should be an assertion error, but there's a bug in NYC-DR (a bug) that will 133 // sometimes give status mixed from multiple SIMs. There's no meaningful message to be displayed 134 // from it, so just suppress the message. 135 LogUtil.e("OmtpVoicemailMessageCreator.create", "Unhandled status: " + status); 136 return null; 137 } 138 isSyncBlockingError(VoicemailStatus status)139 public static boolean isSyncBlockingError(VoicemailStatus status) { 140 if (status.notificationChannelState != Status.NOTIFICATION_CHANNEL_STATE_OK) { 141 return true; 142 } 143 144 if (status.dataChannelState != Status.DATA_CHANNEL_STATE_OK) { 145 return true; 146 } 147 148 switch (status.configurationState) { 149 case Status.CONFIGURATION_STATE_OK: 150 // allow activation to be queued again in case it is interrupted 151 case Status.CONFIGURATION_STATE_CONFIGURING: 152 return false; 153 default: 154 return true; 155 } 156 } 157 158 @Nullable checkQuota( Context context, VoicemailStatus status, VoicemailStatusReader statusReader)159 private static VoicemailErrorMessage checkQuota( 160 Context context, VoicemailStatus status, VoicemailStatusReader statusReader) { 161 if (status.quotaOccupied != Status.QUOTA_UNAVAILABLE 162 && status.quotaTotal != Status.QUOTA_UNAVAILABLE) { 163 return createInboxErrorMessage(context, status, statusReader); 164 } 165 Logger.get(context).logImpression(DialerImpression.Type.VVM_QUOTA_CHECK_UNAVAILABLE); 166 return null; 167 } 168 169 @Nullable createInboxErrorMessage( Context context, VoicemailStatus status, VoicemailStatusReader statusReader)170 private static VoicemailErrorMessage createInboxErrorMessage( 171 Context context, VoicemailStatus status, VoicemailStatusReader statusReader) { 172 173 float voicemailOccupiedFraction = (float) status.quotaOccupied / (float) status.quotaTotal; 174 175 if (voicemailOccupiedFraction < QUOTA_NEAR_FULL_THRESHOLD) { 176 return null; 177 } 178 179 boolean isFull = voicemailOccupiedFraction >= QUOTA_FULL_THRESHOLD; 180 181 PhoneAccountHandle phoneAccountHandle = status.getPhoneAccountHandle(); 182 183 PerAccountSharedPreferences sharedPreferenceForAccount = 184 new PerAccountSharedPreferences( 185 context, phoneAccountHandle, PreferenceManager.getDefaultSharedPreferences(context)); 186 187 VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient(); 188 189 boolean shouldShowPromoForArchive = 190 !isPromoForArchiveDismissed(sharedPreferenceForAccount, isFull) 191 && !voicemailClient.isVoicemailArchiveEnabled(context, phoneAccountHandle) 192 && voicemailClient.isVoicemailArchiveAvailable(context); 193 194 if (!shouldShowPromoForArchive) { 195 if (isFull) { 196 Logger.get(context) 197 .logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_ERROR_MESSAGE); 198 return new VoicemailErrorMessage( 199 context.getString(R.string.voicemail_error_inbox_full_title), 200 context.getString(R.string.voicemail_error_inbox_full_message)); 201 } else { 202 Logger.get(context) 203 .logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_ERROR_MESSAGE); 204 return new VoicemailErrorMessage( 205 context.getString(R.string.voicemail_error_inbox_near_full_title), 206 context.getString(R.string.voicemail_error_inbox_near_full_message)); 207 } 208 } 209 210 String title; 211 CharSequence message; 212 DialerImpression.Type enabledImpression; 213 DialerImpression.Type dismissedImpression; 214 String dismissedKey; 215 216 if (isFull) { 217 Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_FULL_PROMO); 218 title = context.getString(R.string.voicemail_error_inbox_full_turn_archive_on_title); 219 message = context.getText(R.string.voicemail_error_inbox_full_turn_archive_on_message); 220 enabledImpression = DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_FULL_PROMO; 221 dismissedImpression = DialerImpression.Type.VVM_USER_DISMISSED_VM_FULL_PROMO; 222 dismissedKey = VOICEMAIL_PROMO_DISMISSED_KEY; 223 } else { 224 Logger.get(context).logImpression(DialerImpression.Type.VVM_USER_SHOWN_VM_ALMOST_FULL_PROMO); 225 title = context.getString(R.string.voicemail_error_inbox_almost_full_turn_archive_on_title); 226 message = context.getText(R.string.voicemail_error_inbox_almost_full_turn_archive_on_message); 227 enabledImpression = DialerImpression.Type.VVM_USER_ENABLED_ARCHIVE_FROM_VM_ALMOST_FULL_PROMO; 228 dismissedImpression = DialerImpression.Type.VVM_USER_DISMISSED_VM_ALMOST_FULL_PROMO; 229 dismissedKey = VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY; 230 } 231 232 return createVMQuotaPromo( 233 context, 234 phoneAccountHandle, 235 status, 236 statusReader, 237 voicemailClient, 238 sharedPreferenceForAccount, 239 title, 240 message, 241 enabledImpression, 242 dismissedImpression, 243 dismissedKey); 244 } 245 isPromoForArchiveDismissed( PerAccountSharedPreferences sharedPreferenceForAccount, boolean isFull)246 private static boolean isPromoForArchiveDismissed( 247 PerAccountSharedPreferences sharedPreferenceForAccount, boolean isFull) { 248 if (isFull) { 249 return sharedPreferenceForAccount.getBoolean(VOICEMAIL_PROMO_DISMISSED_KEY, false); 250 } else { 251 return sharedPreferenceForAccount.getBoolean( 252 VOICEMAIL_PROMO_ALMOST_FULL_DISMISSED_KEY, false); 253 } 254 } 255 createVMQuotaPromo( Context context, PhoneAccountHandle phoneAccountHandle, VoicemailStatus status, VoicemailStatusReader statusReader, VoicemailClient voicemailClient, PerAccountSharedPreferences sharedPreferenceForAccount, String title, CharSequence message, DialerImpression.Type impressionToLogOnEnable, DialerImpression.Type impressionToLogOnDismiss, String preferenceKeyToUpdate)256 private static VoicemailErrorMessage createVMQuotaPromo( 257 Context context, 258 PhoneAccountHandle phoneAccountHandle, 259 VoicemailStatus status, 260 VoicemailStatusReader statusReader, 261 VoicemailClient voicemailClient, 262 PerAccountSharedPreferences sharedPreferenceForAccount, 263 String title, 264 CharSequence message, 265 DialerImpression.Type impressionToLogOnEnable, 266 DialerImpression.Type impressionToLogOnDismiss, 267 String preferenceKeyToUpdate) { 268 return new VoicemailErrorMessage( 269 title, 270 message, 271 VoicemailErrorMessage.createTurnArchiveOnAction( 272 context, 273 impressionToLogOnEnable, 274 status, 275 statusReader, 276 voicemailClient, 277 phoneAccountHandle), 278 VoicemailErrorMessage.createDismissTurnArchiveOnAction( 279 context, 280 impressionToLogOnDismiss, 281 statusReader, 282 sharedPreferenceForAccount, 283 preferenceKeyToUpdate)); 284 } 285 286 @Nullable createNoSignalMessage( Context context, VoicemailStatus status)287 private static VoicemailErrorMessage createNoSignalMessage( 288 Context context, VoicemailStatus status) { 289 CharSequence title; 290 CharSequence description; 291 List<Action> actions = new ArrayList<>(); 292 if (Status.CONFIGURATION_STATE_OK == status.configurationState) { 293 if (Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED == status.dataChannelState) { 294 title = context.getString(R.string.voicemail_error_no_signal_title); 295 description = 296 context.getString(R.string.voicemail_error_no_signal_cellular_required_message); 297 } else { 298 title = context.getString(R.string.voicemail_error_no_signal_title); 299 if (status.isAirplaneMode) { 300 description = context.getString(R.string.voicemail_error_no_signal_airplane_mode_message); 301 } else { 302 description = context.getString(R.string.voicemail_error_no_signal_message); 303 } 304 actions.add(VoicemailErrorMessage.createSyncAction(context, status)); 305 } 306 } else { 307 title = context.getString(R.string.voicemail_error_not_activate_no_signal_title); 308 if (status.isAirplaneMode) { 309 description = 310 context.getString( 311 R.string.voicemail_error_not_activate_no_signal_airplane_mode_message); 312 } else { 313 description = context.getString(R.string.voicemail_error_not_activate_no_signal_message); 314 actions.add(VoicemailErrorMessage.createRetryAction(context, status)); 315 } 316 } 317 if (status.isAirplaneMode) { 318 actions.add(VoicemailErrorMessage.createChangeAirplaneModeAction(context)); 319 } 320 return new VoicemailErrorMessage(title, description, actions); 321 } 322 } 323