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