1 /* 2 * Copyright (C) 2017 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.app.AlertDialog; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.Intent; 24 import android.content.SharedPreferences; 25 import android.preference.PreferenceManager; 26 import android.support.annotation.Nullable; 27 import android.telecom.PhoneAccountHandle; 28 import android.telephony.TelephonyManager; 29 import android.text.Layout; 30 import android.text.SpannableString; 31 import android.text.Spanned; 32 import android.text.TextUtils; 33 import android.text.style.AlignmentSpan; 34 import android.text.style.TextAppearanceSpan; 35 import android.text.style.URLSpan; 36 import android.view.View; 37 import android.view.View.OnClickListener; 38 import com.android.dialer.common.LogUtil; 39 import com.android.dialer.compat.telephony.TelephonyManagerCompat; 40 import com.android.dialer.configprovider.ConfigProviderComponent; 41 import com.android.dialer.logging.DialerImpression; 42 import com.android.dialer.logging.Logger; 43 import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage.Action; 44 import com.android.voicemail.VisualVoicemailTypeExtensions; 45 import com.android.voicemail.VoicemailClient; 46 import com.android.voicemail.VoicemailComponent; 47 import com.android.voicemail.VoicemailVersionConstants; 48 import java.util.Locale; 49 50 /** 51 * Create error message from {@link VoicemailStatus} for voicemail. This is will show different 52 * terms of service for Verizon and for other carriers. 53 */ 54 public class VoicemailTosMessageCreator { 55 private static final String ISO639_SPANISH = "es"; 56 57 private final Context context; 58 private final VoicemailStatus status; 59 private final VoicemailStatusReader statusReader; 60 private final SharedPreferences preferences; 61 VoicemailTosMessageCreator( final Context context, final VoicemailStatus status, final VoicemailStatusReader statusReader)62 VoicemailTosMessageCreator( 63 final Context context, 64 final VoicemailStatus status, 65 final VoicemailStatusReader statusReader) { 66 this.context = context; 67 this.status = status; 68 this.statusReader = statusReader; 69 this.preferences = 70 PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); 71 } 72 73 @Nullable maybeCreateTosMessage()74 VoicemailErrorMessage maybeCreateTosMessage() { 75 if (!canShowTos()) { 76 return null; 77 } else if (shouldShowTos()) { 78 logTosCreatedImpression(); 79 return getTosMessage(); 80 } else if (shouldShowPromo()) { 81 return getPromoMessage(); 82 } else { 83 return null; 84 } 85 } 86 getTosMessage()87 private VoicemailErrorMessage getTosMessage() { 88 return new VoicemailTosMessage( 89 getNewUserTosTitle(), 90 getNewUserTosMessageText(), 91 new Action( 92 getDeclineText(), 93 new OnClickListener() { 94 @Override 95 public void onClick(View v) { 96 LogUtil.i("VoicemailTosMessageCreator.getTosMessage", "decline clicked"); 97 PhoneAccountHandle handle = 98 new PhoneAccountHandle( 99 ComponentName.unflattenFromString(status.phoneAccountComponentName), 100 status.phoneAccountId); 101 logTosDeclinedImpression(); 102 showDeclineTosDialog(handle); 103 } 104 }), 105 new Action( 106 getAcceptText(), 107 new OnClickListener() { 108 @Override 109 public void onClick(View v) { 110 LogUtil.i("VoicemailTosMessageCreator.getTosMessage", "accept clicked"); 111 if (isVoicemailTranscriptionAvailable()) { 112 VoicemailComponent.get(context) 113 .getVoicemailClient() 114 .setVoicemailTranscriptionEnabled( 115 context, status.getPhoneAccountHandle(), true); 116 } 117 recordTosAcceptance(); 118 // Accepting the TOS also acknowledges the latest features 119 recordFeatureAcknowledgement(); 120 logTosAcceptedImpression(); 121 statusReader.refresh(); 122 } 123 }, 124 true /* raised */)) 125 .setModal(true) 126 .setImageResourceId(R.drawable.voicemail_tos_image); 127 } 128 129 private VoicemailErrorMessage getPromoMessage() { 130 return new VoicemailTosMessage( 131 getExistingUserTosTitle(), 132 getExistingUserTosMessageText(), 133 new Action( 134 context.getString(R.string.dialer_terms_and_conditions_existing_user_decline), 135 new OnClickListener() { 136 @Override 137 public void onClick(View v) { 138 LogUtil.i( 139 "VoicemailTosMessageCreator.getPromoMessage", "declined transcription"); 140 if (isVoicemailTranscriptionAvailable()) { 141 VoicemailClient voicemailClient = 142 VoicemailComponent.get(context).getVoicemailClient(); 143 voicemailClient.setVoicemailTranscriptionEnabled( 144 context, status.getPhoneAccountHandle(), false); 145 // Feature acknowledgement also means accepting TOS, otherwise after removing 146 // the feature ToS, we'll end up showing the ToS 147 // TODO(uabdullah): Consider separating the ToS acceptance and feature 148 // acknowledgment. 149 recordTosAcceptance(); 150 recordFeatureAcknowledgement(); 151 statusReader.refresh(); 152 } else { 153 LogUtil.e( 154 "VoicemailTosMessageCreator.getPromoMessage", 155 "voicemail transcription not available"); 156 } 157 } 158 }), 159 new Action( 160 context.getString(R.string.dialer_terms_and_conditions_existing_user_ack), 161 new OnClickListener() { 162 @Override 163 public void onClick(View v) { 164 LogUtil.i("VoicemailTosMessageCreator.getPromoMessage", "acknowledge clicked"); 165 if (isVoicemailTranscriptionAvailable()) { 166 VoicemailComponent.get(context) 167 .getVoicemailClient() 168 .setVoicemailTranscriptionEnabled( 169 context, status.getPhoneAccountHandle(), true); 170 } 171 // Feature acknowledgement also means accepting TOS 172 recordTosAcceptance(); 173 recordFeatureAcknowledgement(); 174 statusReader.refresh(); 175 } 176 }, 177 true /* raised */)) 178 .setModal(true) 179 .setImageResourceId(R.drawable.voicemail_tos_image); 180 } 181 182 private boolean canShowTos() { 183 if (!isValidVoicemailType(status.type)) { 184 LogUtil.i("VoicemailTosMessageCreator.canShowTos", "unsupported type: " + status.type); 185 return false; 186 } 187 188 if (status.getPhoneAccountHandle() == null 189 || status.getPhoneAccountHandle().getComponentName() == null) { 190 LogUtil.i("VoicemailTosMessageCreator.canShowTos", "invalid phone account"); 191 return false; 192 } 193 194 return true; 195 } 196 197 private boolean shouldShowTos() { 198 if (hasAcceptedTos()) { 199 LogUtil.i("VoicemailTosMessageCreator.shouldShowTos", "already accepted TOS"); 200 return false; 201 } 202 203 if (isVvm3()) { 204 LogUtil.i("VoicemailTosMessageCreator.shouldShowTos", "showing TOS for verizon"); 205 return true; 206 } 207 208 if (isVoicemailTranscriptionAvailable() && !isLegacyVoicemailUser()) { 209 LogUtil.i( 210 "VoicemailTosMessageCreator.shouldShowTos", "showing TOS for Google transcription users"); 211 return true; 212 } 213 214 return false; 215 } 216 217 private boolean shouldShowPromo() { 218 if (hasAcknowledgedFeatures()) { 219 LogUtil.i( 220 "VoicemailTosMessageCreator.shouldShowPromo", "already acknowledeged latest features"); 221 return false; 222 } 223 224 if (isVoicemailTranscriptionAvailable()) { 225 LogUtil.i( 226 "VoicemailTosMessageCreator.shouldShowPromo", 227 "showing promo for Google transcription users"); 228 return true; 229 } 230 231 return false; 232 } 233 234 private static boolean isValidVoicemailType(String type) { 235 if (type == null) { 236 return false; 237 } 238 switch (type) { 239 case TelephonyManager.VVM_TYPE_OMTP: 240 case TelephonyManager.VVM_TYPE_CVVM: 241 case VisualVoicemailTypeExtensions.VVM_TYPE_VVM3: 242 return true; 243 default: 244 return false; 245 } 246 } 247 248 private boolean isVoicemailTranscriptionAvailable() { 249 return VoicemailComponent.get(context) 250 .getVoicemailClient() 251 .isVoicemailTranscriptionAvailable(context, status.getPhoneAccountHandle()); 252 } 253 254 private void showDeclineTosDialog(final PhoneAccountHandle handle) { 255 if (isVvm3() && Vvm3VoicemailMessageCreator.PIN_NOT_SET == status.configurationState) { 256 LogUtil.i( 257 "VoicemailTosMessageCreator.showDeclineTosDialog", "PIN_NOT_SET, showing set PIN dialog"); 258 showSetPinBeforeDeclineDialog(handle); 259 return; 260 } 261 LogUtil.i( 262 "VoicemailTosMessageCreator.showDeclineVerizonTosDialog", 263 "showing decline ToS dialog, status=" + status); 264 final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); 265 AlertDialog.Builder builder = new AlertDialog.Builder(context); 266 builder.setTitle(R.string.terms_and_conditions_decline_dialog_title); 267 builder.setMessage(getTosDeclinedDialogMessageId()); 268 builder.setPositiveButton( 269 getTosDeclinedDialogDowngradeId(), 270 new DialogInterface.OnClickListener() { 271 @Override 272 public void onClick(DialogInterface dialog, int which) { 273 Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINED); 274 VoicemailClient voicemailClient = VoicemailComponent.get(context).getVoicemailClient(); 275 if (voicemailClient.isVoicemailModuleEnabled()) { 276 voicemailClient.setVoicemailEnabled(context, status.getPhoneAccountHandle(), false); 277 } else { 278 TelephonyManagerCompat.setVisualVoicemailEnabled(telephonyManager, handle, false); 279 } 280 } 281 }); 282 283 builder.setNegativeButton( 284 android.R.string.cancel, 285 new DialogInterface.OnClickListener() { 286 @Override 287 public void onClick(DialogInterface dialog, int which) { 288 dialog.dismiss(); 289 } 290 }); 291 292 builder.setCancelable(true); 293 builder.show(); 294 } 295 296 private void showSetPinBeforeDeclineDialog(PhoneAccountHandle phoneAccountHandle) { 297 AlertDialog.Builder builder = new AlertDialog.Builder(context); 298 builder.setMessage(R.string.verizon_terms_and_conditions_decline_set_pin_dialog_message); 299 builder.setPositiveButton( 300 R.string.verizon_terms_and_conditions_decline_set_pin_dialog_set_pin, 301 new DialogInterface.OnClickListener() { 302 @Override 303 public void onClick(DialogInterface dialog, int which) { 304 Logger.get(context) 305 .logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_DECLINE_CHANGE_PIN_SHOWN); 306 Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL); 307 intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle); 308 context.startActivity(intent); 309 } 310 }); 311 312 builder.setNegativeButton( 313 android.R.string.cancel, 314 new DialogInterface.OnClickListener() { 315 @Override 316 public void onClick(DialogInterface dialog, int which) { 317 dialog.dismiss(); 318 } 319 }); 320 321 builder.setCancelable(true); 322 builder.show(); 323 } 324 325 private boolean isVvm3() { 326 return VisualVoicemailTypeExtensions.VVM_TYPE_VVM3.equals(status.type); 327 } 328 329 private boolean useSpanish() { 330 return Locale.getDefault().getLanguage().equals(new Locale(ISO639_SPANISH).getLanguage()); 331 } 332 333 private boolean hasAcceptedTos() { 334 if (isVvm3()) { 335 return preferences.getInt(VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 0) 336 >= VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION; 337 } else { 338 return preferences.getInt(VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 0) 339 >= VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION; 340 } 341 } 342 343 private void recordTosAcceptance() { 344 if (isVvm3()) { 345 preferences 346 .edit() 347 .putInt( 348 VoicemailVersionConstants.PREF_VVM3_TOS_VERSION_ACCEPTED_KEY, 349 VoicemailVersionConstants.CURRENT_VVM3_TOS_VERSION) 350 .apply(); 351 } else { 352 preferences 353 .edit() 354 .putInt( 355 VoicemailVersionConstants.PREF_DIALER_TOS_VERSION_ACCEPTED_KEY, 356 VoicemailVersionConstants.CURRENT_DIALER_TOS_VERSION) 357 .apply(); 358 } 359 360 PhoneAccountHandle handle = 361 new PhoneAccountHandle( 362 ComponentName.unflattenFromString(status.phoneAccountComponentName), 363 status.phoneAccountId); 364 VoicemailComponent.get(context).getVoicemailClient().onTosAccepted(context, handle); 365 } 366 367 private boolean hasAcknowledgedFeatures() { 368 if (isVvm3()) { 369 return true; 370 } 371 372 return preferences.getInt( 373 VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0) 374 >= VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION; 375 } 376 377 private void recordFeatureAcknowledgement() { 378 preferences 379 .edit() 380 .putInt( 381 VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 382 VoicemailVersionConstants.CURRENT_VOICEMAIL_FEATURE_VERSION) 383 .apply(); 384 } 385 386 private boolean isLegacyVoicemailUser() { 387 return preferences.getInt( 388 VoicemailVersionConstants.PREF_DIALER_FEATURE_VERSION_ACKNOWLEDGED_KEY, 0) 389 == VoicemailVersionConstants.LEGACY_VOICEMAIL_FEATURE_VERSION; 390 } 391 392 private void logTosCreatedImpression() { 393 if (isVvm3()) { 394 Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_CREATED); 395 } else { 396 Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_CREATED); 397 } 398 } 399 400 private void logTosDeclinedImpression() { 401 if (isVvm3()) { 402 Logger.get(context) 403 .logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_DECLINE_CLICKED); 404 } else { 405 Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_DECLINE_CLICKED); 406 } 407 } 408 409 private void logTosAcceptedImpression() { 410 if (isVvm3()) { 411 Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_VVM3_TOS_V2_ACCEPTED); 412 } else { 413 Logger.get(context).logImpression(DialerImpression.Type.VOICEMAIL_DIALER_TOS_ACCEPTED); 414 } 415 } 416 417 private CharSequence getVvm3Tos() { 418 String policyUrl = context.getString(R.string.verizon_terms_and_conditions_policy_url); 419 return useSpanish() 420 ? context.getString(R.string.verizon_terms_and_conditions_1_1_spanish, policyUrl) 421 : context.getString(R.string.verizon_terms_and_conditions_1_1_english, policyUrl); 422 } 423 424 private CharSequence getVvmDialerTos() { 425 return context.getString(R.string.dialer_terms_and_conditions_for_verizon_1_0); 426 } 427 428 private CharSequence getNewUserDialerTos() { 429 if (!isVoicemailTranscriptionAvailable()) { 430 return ""; 431 } 432 433 String learnMoreText = context.getString(R.string.dialer_terms_and_conditions_learn_more); 434 return context.getString(R.string.dialer_terms_and_conditions_1_0, learnMoreText); 435 } 436 437 private CharSequence getExistingUserDialerTos() { 438 if (!isVoicemailTranscriptionAvailable()) { 439 return ""; 440 } 441 442 String learnMoreText = context.getString(R.string.dialer_terms_and_conditions_learn_more); 443 return context.getString(R.string.dialer_terms_and_conditions_existing_user, learnMoreText); 444 } 445 446 private CharSequence getAcceptText() { 447 if (isVvm3()) { 448 return useSpanish() 449 ? context.getString(R.string.verizon_terms_and_conditions_accept_spanish) 450 : context.getString(R.string.verizon_terms_and_conditions_accept_english); 451 } else { 452 return useSpanish() 453 ? context.getString(R.string.dialer_terms_and_conditions_accept_spanish) 454 : context.getString(R.string.dialer_terms_and_conditions_accept_english); 455 } 456 } 457 458 private CharSequence getDeclineText() { 459 if (isVvm3()) { 460 return useSpanish() 461 ? context.getString(R.string.verizon_terms_and_conditions_decline_spanish) 462 : context.getString(R.string.verizon_terms_and_conditions_decline_english); 463 } else { 464 return useSpanish() 465 ? context.getString(R.string.dialer_terms_and_conditions_decline_spanish) 466 : context.getString(R.string.dialer_terms_and_conditions_decline_english); 467 } 468 } 469 470 private CharSequence getNewUserTosTitle() { 471 return isVvm3() 472 ? context.getString(R.string.verizon_terms_and_conditions_title) 473 : context.getString(R.string.dialer_terms_and_conditions_title); 474 } 475 476 private CharSequence getExistingUserTosTitle() { 477 return isVvm3() 478 ? context.getString(R.string.verizon_terms_and_conditions_title) 479 : context.getString(R.string.dialer_terms_and_conditions_existing_user_title); 480 } 481 482 private CharSequence getNewUserTosMessageText() { 483 SpannableString spannableTos; 484 if (isVvm3()) { 485 // For verizon the TOS consist of three pieces: google dialer TOS, Verizon TOS message and 486 // Verizon TOS details. 487 CharSequence vvm3Details = getVvm3Tos(); 488 CharSequence tos = 489 context.getString( 490 R.string.verizon_terms_and_conditions_message, getVvmDialerTos(), vvm3Details); 491 spannableTos = new SpannableString(tos); 492 // Set the text style for the details part of the TOS 493 int start = spannableTos.length() - vvm3Details.length(); 494 spannableTos.setSpan( 495 new TextAppearanceSpan(context, R.style.TosDetailsTextStyle), 496 start, 497 start + vvm3Details.length(), 498 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 499 // Add verizon policy link 500 String linkUrl = context.getString(R.string.verizon_terms_and_conditions_policy_url); 501 return addLink(spannableTos, linkUrl, linkUrl); 502 } else { 503 // The TOS for everyone else, there are no details, but change to center alignment. 504 CharSequence tos = 505 context.getString(R.string.dialer_terms_and_conditions_message, getNewUserDialerTos()); 506 spannableTos = new SpannableString(tos); 507 spannableTos.setSpan( 508 new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 509 0, 510 tos.length(), 511 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 512 513 // Add 'Learn more' link for dialer TOS 514 String learnMore = context.getString(R.string.dialer_terms_and_conditions_learn_more); 515 return addLink(spannableTos, learnMore, getLearnMoreUrl()); 516 } 517 } 518 519 private CharSequence getExistingUserTosMessageText() { 520 SpannableString spannableTos; 521 // Change to center alignment. 522 CharSequence tos = 523 context.getString(R.string.dialer_terms_and_conditions_message, getExistingUserDialerTos()); 524 spannableTos = new SpannableString(tos); 525 spannableTos.setSpan( 526 new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 527 0, 528 tos.length(), 529 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 530 531 // Add 'Learn more' link for dialer TOS 532 String learnMore = context.getString(R.string.dialer_terms_and_conditions_learn_more); 533 return addLink(spannableTos, learnMore, getLearnMoreUrl()); 534 } 535 536 private SpannableString addLink(SpannableString spannable, String linkText, String linkUrl) { 537 if (TextUtils.isEmpty(linkUrl) || TextUtils.isEmpty(linkText)) { 538 return spannable; 539 } 540 541 int start = spannable.toString().indexOf(linkText); 542 if (start != -1) { 543 int end = start + linkText.length(); 544 spannable.setSpan(new URLSpan(linkUrl), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 545 spannable.setSpan( 546 new TextAppearanceSpan(context, R.style.TosLinkStyle), 547 start, 548 end, 549 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 550 } 551 return spannable; 552 } 553 554 private String getLearnMoreUrl() { 555 return ConfigProviderComponent.get(context) 556 .getConfigProvider() 557 .getString( 558 "voicemail_transcription_learn_more_url", 559 context.getString(R.string.dialer_terms_and_conditions_learn_more_url)); 560 } 561 562 private int getTosDeclinedDialogMessageId() { 563 return isVvm3() 564 ? R.string.verizon_terms_and_conditions_decline_dialog_message 565 : R.string.dialer_terms_and_conditions_decline_dialog_message; 566 } 567 568 private int getTosDeclinedDialogDowngradeId() { 569 return isVvm3() 570 ? R.string.verizon_terms_and_conditions_decline_dialog_downgrade 571 : R.string.dialer_terms_and_conditions_decline_dialog_downgrade; 572 } 573 } 574