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