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 package com.android.settings.dashboard; 17 18 import android.accounts.Account; 19 import android.annotation.DrawableRes; 20 import android.annotation.LayoutRes; 21 import android.annotation.StringRes; 22 import android.app.Activity; 23 import android.app.ActivityManager; 24 import android.app.DialogFragment; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.os.Bundle; 29 import android.support.annotation.VisibleForTesting; 30 import android.support.v7.widget.RecyclerView; 31 import android.text.TextUtils; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.widget.AdapterView; 36 import android.widget.ArrayAdapter; 37 import android.widget.ImageView; 38 import android.widget.Spinner; 39 import android.widget.TextView; 40 41 import com.android.internal.logging.nano.MetricsProto; 42 import com.android.internal.util.ArrayUtils; 43 import com.android.settings.R; 44 import com.android.settings.core.instrumentation.MetricsFeatureProvider; 45 import com.android.settings.overlay.SupportFeatureProvider; 46 import com.android.settings.support.SupportDisclaimerDialogFragment; 47 import com.android.settings.support.SupportPhone; 48 import com.android.settings.support.SupportPhoneDialogFragment; 49 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.List; 53 54 import static com.android.settings.overlay.SupportFeatureProvider.SupportType.CHAT; 55 import static com.android.settings.overlay.SupportFeatureProvider.SupportType.PHONE; 56 57 /** 58 * Item adapter for support tiles. 59 */ 60 public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAdapter.ViewHolder> { 61 62 private static final String STATE_SELECTED_COUNTRY = "STATE_SELECTED_COUNTRY"; 63 private static final String ACCOUNT_SELECTED_INDEX = "ACCOUNT_SELECTED_INDEX"; 64 private static final int TYPE_ESCALATION_OPTIONS = R.layout.support_escalation_options; 65 private static final int TYPE_ESCALATION_OPTIONS_OFFLINE = 66 R.layout.support_offline_escalation_options; 67 private static final int TYPE_SUPPORT_TILE = R.layout.support_tile; 68 private static final int TYPE_SUPPORT_TILE_SPACER = R.layout.support_tile_spacer; 69 private static final int TYPE_SIGN_IN_BUTTON = R.layout.support_sign_in_button; 70 71 private final Activity mActivity; 72 private final EscalationClickListener mEscalationClickListener; 73 private final OfflineSpinnerItemSelectListener mOfflineSpinnerItemSelectListener; 74 private final OnlineSpinnerItemSelectListener mOnlineSpinnerItemSelectListener; 75 private final SupportFeatureProvider mSupportFeatureProvider; 76 private final MetricsFeatureProvider mMetricsFeatureProvider; 77 private final View.OnClickListener mItemClickListener; 78 private final List<SupportData> mSupportData; 79 80 private String mSelectedCountry; 81 private boolean mHasInternet; 82 private Account[] mAccounts; 83 private int mSelectedAccountIndex; 84 85 public SupportItemAdapter(Activity activity, Bundle savedInstanceState, 86 SupportFeatureProvider supportFeatureProvider, 87 MetricsFeatureProvider metricsFeatureProvider, 88 View.OnClickListener itemClickListener) { 89 mActivity = activity; 90 mSupportFeatureProvider = supportFeatureProvider; 91 mMetricsFeatureProvider = metricsFeatureProvider; 92 mItemClickListener = itemClickListener; 93 mEscalationClickListener = new EscalationClickListener(); 94 mOfflineSpinnerItemSelectListener = new OfflineSpinnerItemSelectListener(); 95 mOnlineSpinnerItemSelectListener = new OnlineSpinnerItemSelectListener(); 96 mSupportData = new ArrayList<>(); 97 // Optimistically assume we have Internet access. It will be updated later to correct value. 98 mHasInternet = true; 99 if (savedInstanceState != null) { 100 mSelectedCountry = savedInstanceState.getString(STATE_SELECTED_COUNTRY); 101 mSelectedAccountIndex = savedInstanceState.getInt(ACCOUNT_SELECTED_INDEX); 102 } else { 103 mSelectedCountry = mSupportFeatureProvider.getCurrentCountryCodeIfHasConfig(PHONE); 104 mSelectedAccountIndex = 0; 105 } 106 107 mAccounts = mSupportFeatureProvider.getSupportEligibleAccounts(mActivity); 108 refreshData(); 109 } 110 111 @Override 112 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 113 return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate( 114 viewType, parent, false)); 115 } 116 117 @Override 118 public void onBindViewHolder(ViewHolder holder, int position) { 119 final SupportData data = mSupportData.get(position); 120 switch (holder.getItemViewType()) { 121 case TYPE_SIGN_IN_BUTTON: 122 bindSignInPromoTile(holder, (EscalationData) data); 123 break; 124 case TYPE_ESCALATION_OPTIONS: 125 bindEscalationOptions(holder, (EscalationData) data); 126 break; 127 case TYPE_ESCALATION_OPTIONS_OFFLINE: 128 bindOfflineEscalationOptions(holder, (OfflineEscalationData) data); 129 break; 130 case TYPE_SUPPORT_TILE_SPACER: 131 break; 132 default: 133 bindSupportTile(holder, data); 134 break; 135 } 136 } 137 138 @Override 139 public int getItemViewType(int position) { 140 return mSupportData.get(position).type; 141 } 142 143 @Override 144 public int getItemCount() { 145 return mSupportData.size(); 146 } 147 148 /** 149 * Called when a support item is clicked. 150 */ 151 public void onItemClicked(int position) { 152 if (position >= 0 && position < mSupportData.size()) { 153 final SupportData data = mSupportData.get(position); 154 if (data.intent != null && 155 mActivity.getPackageManager().resolveActivity(data.intent, 0) != null) { 156 if (data.metricsEvent >= 0) { 157 mMetricsFeatureProvider.action(mActivity, data.metricsEvent); 158 } 159 mActivity.startActivityForResult(data.intent, 0); 160 } 161 } 162 } 163 164 public void setHasInternet(boolean hasInternet) { 165 if (mHasInternet != hasInternet) { 166 mHasInternet = hasInternet; 167 refreshEscalationCards(); 168 } 169 } 170 171 public void setAccounts(Account accounts[]) { 172 if (!Arrays.equals(mAccounts, accounts)) { 173 if (mAccounts.length == 0) { 174 mSelectedAccountIndex = 0; 175 } else { 176 final int index = ArrayUtils.indexOf(accounts, mAccounts[mSelectedAccountIndex]); 177 mSelectedAccountIndex = index != -1 ? index : 0; 178 } 179 180 mAccounts = accounts; 181 mSupportFeatureProvider.refreshOperationRules(); 182 refreshEscalationCards(); 183 } 184 } 185 186 public void onSaveInstanceState(Bundle outState) { 187 outState.putString(STATE_SELECTED_COUNTRY, mSelectedCountry); 188 outState.putInt(ACCOUNT_SELECTED_INDEX, mSelectedAccountIndex); 189 } 190 191 /** 192 * Create data for the adapter. If there is already data in the adapter, they will be 193 * destroyed and recreated. 194 */ 195 void refreshData() { 196 mSupportData.clear(); 197 addEscalationCards(); 198 addMoreHelpItems(); 199 notifyDataSetChanged(); 200 } 201 202 /** 203 * Adds 1 escalation card. Based on current phone state, the escalation card can display 204 * different content. 205 */ 206 private void addEscalationCards() { 207 if (mAccounts.length == 0) { 208 addSignInPromo(); 209 } else if (mHasInternet) { 210 addOnlineEscalationCards(); 211 } else { 212 addOfflineEscalationCards(); 213 } 214 } 215 216 /** 217 * Finds and refreshes escalation card data. 218 */ 219 private void refreshEscalationCards() { 220 if (getItemCount() > 0) { 221 final int itemType = getItemViewType(0 /* position */); 222 if (itemType == TYPE_SIGN_IN_BUTTON 223 || itemType == TYPE_ESCALATION_OPTIONS 224 || itemType == TYPE_ESCALATION_OPTIONS_OFFLINE) { 225 mSupportData.remove(0 /* position */); 226 addEscalationCards(); 227 notifyItemChanged(0 /* position */); 228 } 229 } 230 } 231 232 private void addOnlineEscalationCards() { 233 final boolean hasPhoneOperation = 234 mSupportFeatureProvider.isSupportTypeEnabled(mActivity, PHONE); 235 final boolean hasChatOperation = 236 mSupportFeatureProvider.isSupportTypeEnabled(mActivity, CHAT); 237 final EscalationData.Builder builder = new EscalationData.Builder(mActivity); 238 if (!hasPhoneOperation && !hasChatOperation) { 239 // No support at all. 240 builder.setTileTitle(R.string.support_escalation_title) 241 .setTileSummary(R.string.support_escalation_unavailable_summary); 242 } else if (mSupportFeatureProvider.isAlwaysOperating(PHONE, null /* countryCode */) 243 || mSupportFeatureProvider.isAlwaysOperating(CHAT, null /* countryCode */)) { 244 // Support is available. 245 builder.setTileTitle(R.string.support_escalation_24_7_title) 246 .setTileTitleDescription(R.string.support_escalation_24_7_content_description) 247 .setTileSummary(mActivity.getString(R.string.support_escalation_24_7_summary)); 248 } else if (mSupportFeatureProvider.isOperatingNow(PHONE) 249 || mSupportFeatureProvider.isOperatingNow(CHAT)) { 250 // Support is available now. 251 builder.setTileTitle(R.string.support_escalation_title) 252 .setTileSummary(R.string.support_escalation_summary); 253 } else { 254 // Support is now temporarily unavailable. 255 builder.setTileTitle(R.string.support_escalation_title) 256 .setTileSummary( 257 mSupportFeatureProvider.getOperationHours(mActivity, PHONE, null, 258 true /* hasInternet */)); 259 } 260 if (hasPhoneOperation) { 261 builder.setText1(R.string.support_escalation_by_phone) 262 .setSummary1(mSupportFeatureProvider.getEstimatedWaitTime(mActivity, PHONE)) 263 .setEnabled1(mSupportFeatureProvider.isOperatingNow(PHONE)); 264 } 265 if (hasChatOperation) { 266 builder.setText2(R.string.support_escalation_by_chat) 267 .setSummary2(mSupportFeatureProvider.getEstimatedWaitTime(mActivity, CHAT)) 268 .setEnabled2(mSupportFeatureProvider.isOperatingNow(CHAT)); 269 } 270 mSupportData.add(0 /* index */, builder.build()); 271 } 272 273 private void addOfflineEscalationCards() { 274 final CharSequence operatingHours; 275 final boolean isPhoneSupportAlwaysOperating = 276 mSupportFeatureProvider.isAlwaysOperating(PHONE, mSelectedCountry); 277 if (isPhoneSupportAlwaysOperating) { 278 operatingHours = mActivity.getString(R.string.support_escalation_24_7_summary); 279 } else { 280 operatingHours = mSupportFeatureProvider.getOperationHours(mActivity, 281 PHONE, mSelectedCountry, false /* hasInternet */); 282 } 283 mSupportData.add(0 /* index */, new OfflineEscalationData.Builder(mActivity) 284 .setCountries(mSupportFeatureProvider.getPhoneSupportCountries()) 285 .setTollFreePhone(mSupportFeatureProvider.getSupportPhones( 286 mSelectedCountry, true /* isTollFree */)) 287 .setTolledPhone(mSupportFeatureProvider.getSupportPhones( 288 mSelectedCountry, false /* isTollFree */)) 289 .setTileTitle(isPhoneSupportAlwaysOperating 290 ? R.string.support_escalation_24_7_title 291 : R.string.support_escalation_title) 292 .setTileTitleDescription(isPhoneSupportAlwaysOperating 293 ? R.string.support_escalation_24_7_content_description 294 : R.string.support_escalation_title) 295 .setTileSummary(operatingHours) 296 .build()); 297 } 298 299 private void addSignInPromo() { 300 mSupportData.add(0 /* index */, new EscalationData.Builder(mActivity, TYPE_SIGN_IN_BUTTON) 301 .setText1(R.string.support_sign_in_button_text) 302 .setText2(R.string.support_sign_in_required_help) 303 .setTileTitle(R.string.support_sign_in_required_title) 304 .setTileSummary(R.string.support_sign_in_required_summary) 305 .build()); 306 } 307 308 private void addMoreHelpItems() { 309 mSupportData.add(new SupportData.Builder(mActivity, TYPE_SUPPORT_TILE_SPACER).build()); 310 PackageManager packageManager = mActivity.getPackageManager(); 311 Intent intent = mSupportFeatureProvider.getHelpIntent(mActivity); 312 if (packageManager.resolveActivity(intent, 0) != null) { 313 mSupportData.add(new SupportData.Builder(mActivity, TYPE_SUPPORT_TILE) 314 .setIcon(R.drawable.ic_help_24dp) 315 .setTileTitle(R.string.support_help_feedback_title) 316 .setIntent(intent) 317 .setMetricsEvent(MetricsProto.MetricsEvent.ACTION_SUPPORT_HELP_AND_FEEDBACK) 318 .build()); 319 } 320 intent = mSupportFeatureProvider.getTipsAndTricksIntent(mActivity); 321 if (packageManager.resolveActivity(intent, 0) != null) { 322 mSupportData.add(new SupportData.Builder(mActivity, TYPE_SUPPORT_TILE) 323 .setIcon(R.drawable.ic_lightbulb_outline_24) 324 .setTileTitle(R.string.support_tips_and_tricks_title) 325 .setIntent(intent) 326 .setMetricsEvent(MetricsProto.MetricsEvent.ACTION_SUPPORT_TIPS_AND_TRICKS) 327 .build()); 328 } 329 } 330 331 private void bindEscalationOptions(ViewHolder holder, EscalationData data) { 332 holder.tileTitleView.setText(data.tileTitle); 333 holder.tileTitleView.setContentDescription(data.tileTitleDescription); 334 holder.tileSummaryView.setText(data.tileSummary); 335 if (data.text1 == 0) { 336 holder.text1View.setVisibility(View.GONE); 337 } else { 338 holder.text1View.setText(data.text1); 339 holder.text1View.setOnClickListener(mEscalationClickListener); 340 holder.text1View.setEnabled(data.enabled1 && mHasInternet); 341 holder.text1View.setVisibility(View.VISIBLE); 342 } 343 if (TextUtils.isEmpty(data.text2)) { 344 holder.text2View.setVisibility(View.GONE); 345 } else { 346 holder.text2View.setText(data.text2); 347 holder.text2View.setOnClickListener(mEscalationClickListener); 348 holder.text2View.setEnabled(data.enabled2 && mHasInternet); 349 holder.text2View.setVisibility(View.VISIBLE); 350 } 351 if (holder.summary1View != null) { 352 holder.summary1View.setText(data.summary1); 353 holder.summary1View.setVisibility(mHasInternet && !TextUtils.isEmpty(data.summary1) 354 ? View.VISIBLE : View.GONE); 355 } 356 if (holder.summary2View != null) { 357 holder.summary2View.setText(data.summary2); 358 holder.summary2View.setVisibility(mHasInternet && !TextUtils.isEmpty(data.summary2) 359 ? View.VISIBLE : View.GONE); 360 } 361 362 bindAccountPicker(holder); 363 } 364 365 @VisibleForTesting 366 public void bindAccountPicker(ViewHolder holder) { 367 final Spinner spinner = (Spinner) holder.itemView.findViewById(R.id.account_spinner); 368 369 final ArrayAdapter<String> adapter = new ArrayAdapter( 370 mActivity, R.layout.support_account_spinner_item, 371 extractAccountNames(mAccounts)); 372 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 373 spinner.setAdapter(adapter); 374 spinner.setOnItemSelectedListener(mOnlineSpinnerItemSelectListener); 375 spinner.setSelection(mSelectedAccountIndex); 376 } 377 378 private void bindOfflineEscalationOptions(ViewHolder holder, OfflineEscalationData data) { 379 // Bind Title 380 holder.tileTitleView.setText(data.tileTitle); 381 holder.tileTitleView.setContentDescription(data.tileTitleDescription); 382 holder.tileSummaryView.setText(data.tileSummary); 383 // Bind spinner 384 final Spinner spinner = (Spinner) holder.itemView.findViewById(R.id.spinner); 385 final ArrayAdapter<String> adapter = new ArrayAdapter( 386 mActivity, android.R.layout.simple_spinner_dropdown_item, data.countries); 387 spinner.setAdapter(adapter); 388 final List<String> countryCodes = mSupportFeatureProvider.getPhoneSupportCountryCodes(); 389 for (int i = 0; i < countryCodes.size(); i++) { 390 if (TextUtils.equals(countryCodes.get(i), mSelectedCountry)) { 391 spinner.setSelection(i); 392 break; 393 } 394 } 395 spinner.setOnItemSelectedListener(mOfflineSpinnerItemSelectListener); 396 // Bind buttons 397 if (data.tollFreePhone != null) { 398 holder.text1View.setText(data.tollFreePhone.number); 399 holder.text1View.setVisibility(View.VISIBLE); 400 holder.text1View.setOnClickListener(mEscalationClickListener); 401 } else { 402 holder.text1View.setVisibility(View.GONE); 403 } 404 if (data.tolledPhone != null) { 405 holder.text2View.setText( 406 mActivity.getString(R.string.support_international_phone_title)); 407 holder.text2View.setVisibility(View.VISIBLE); 408 holder.text2View.setOnClickListener(mEscalationClickListener); 409 } else { 410 holder.text2View.setVisibility(View.GONE); 411 } 412 413 if (ActivityManager.isUserAMonkey()) { 414 holder.text1View.setVisibility(View.GONE); 415 holder.text2View.setVisibility(View.GONE); 416 spinner.setVisibility(View.GONE); 417 holder.itemView.findViewById(R.id.support_text).setVisibility(View.GONE); 418 } 419 } 420 421 private void bindSignInPromoTile(ViewHolder holder, EscalationData data) { 422 holder.tileTitleView.setText(data.tileTitle); 423 holder.tileTitleView.setContentDescription(data.tileTitleDescription); 424 holder.tileSummaryView.setText(data.tileSummary); 425 holder.text1View.setText(data.text1); 426 holder.text2View.setText(data.text2); 427 holder.text1View.setOnClickListener(mEscalationClickListener); 428 holder.text2View.setOnClickListener(mEscalationClickListener); 429 } 430 431 private void bindSupportTile(ViewHolder holder, SupportData data) { 432 if (holder.iconView != null) { 433 holder.iconView.setImageResource(data.icon); 434 } 435 if (holder.tileTitleView != null) { 436 holder.tileTitleView.setText(data.tileTitle); 437 holder.tileTitleView.setContentDescription(data.tileTitleDescription); 438 } 439 if (holder.tileSummaryView != null) { 440 holder.tileSummaryView.setText(data.tileSummary); 441 } 442 holder.itemView.setOnClickListener(mItemClickListener); 443 } 444 445 /** 446 * Show a disclaimer dialog and start support action after disclaimer has been acknowledged. 447 */ 448 private void tryStartDisclaimerAndSupport(final @SupportFeatureProvider.SupportType int type) { 449 if (mSupportFeatureProvider.shouldShowDisclaimerDialog(mActivity)) { 450 DialogFragment fragment = SupportDisclaimerDialogFragment.newInstance( 451 mAccounts[mSelectedAccountIndex], type); 452 fragment.show(mActivity.getFragmentManager(), SupportDisclaimerDialogFragment.TAG); 453 return; 454 } 455 mSupportFeatureProvider.startSupport(mActivity, mAccounts[mSelectedAccountIndex], type); 456 } 457 458 private String[] extractAccountNames(Account[] accounts) { 459 String[] accountNames = new String[accounts.length+1]; 460 for (int i = 0; i < accounts.length; i++) { 461 accountNames[i] = accounts[i].name; 462 } 463 accountNames[accounts.length] = mActivity.getString( 464 R.string.support_account_picker_add_account); 465 466 return accountNames; 467 } 468 469 /** 470 * Click handler for starting escalation options. 471 */ 472 private final class EscalationClickListener implements View.OnClickListener { 473 @Override 474 public void onClick(final View v) { 475 if (mAccounts.length == 0) { 476 switch (v.getId()) { 477 case android.R.id.text1: 478 mMetricsFeatureProvider.action(mActivity, 479 MetricsProto.MetricsEvent.ACTION_SUPPORT_SIGN_IN); 480 mActivity.startActivityForResult( 481 mSupportFeatureProvider.getAccountLoginIntent(), 482 0 /* requestCode */); 483 break; 484 case android.R.id.text2: 485 mActivity.startActivityForResult( 486 mSupportFeatureProvider.getSignInHelpIntent(mActivity), 487 0 /* requestCode */); 488 break; 489 } 490 } else if (mHasInternet) { 491 switch (v.getId()) { 492 case android.R.id.text1: 493 mMetricsFeatureProvider.action(mActivity, 494 MetricsProto.MetricsEvent.ACTION_SUPPORT_PHONE); 495 tryStartDisclaimerAndSupport(PHONE); 496 break; 497 case android.R.id.text2: 498 mMetricsFeatureProvider.action(mActivity, 499 MetricsProto.MetricsEvent.ACTION_SUPPORT_CHAT); 500 tryStartDisclaimerAndSupport(CHAT); 501 break; 502 } 503 } else { 504 switch (v.getId()) { 505 case android.R.id.text1: { 506 final SupportPhone phone = mSupportFeatureProvider 507 .getSupportPhones(mSelectedCountry, true /* isTollFree */); 508 if (phone != null) { 509 final Intent intent = phone.getDialIntent(); 510 final boolean canDial = !mActivity.getPackageManager() 511 .queryIntentActivities(intent, 0) 512 .isEmpty(); 513 if (canDial) { 514 mMetricsFeatureProvider.action(mActivity, 515 MetricsProto.MetricsEvent.ACTION_SUPPORT_DAIL_TOLLFREE); 516 mActivity.startActivity(intent); 517 } 518 } 519 break; 520 } 521 case android.R.id.text2: { 522 final SupportPhone phone = mSupportFeatureProvider 523 .getSupportPhones(mSelectedCountry, false /* isTollFree */); 524 final SupportPhoneDialogFragment fragment = 525 SupportPhoneDialogFragment.newInstance(phone); 526 mMetricsFeatureProvider.action(mActivity, 527 MetricsProto.MetricsEvent.ACTION_SUPPORT_VIEW_TRAVEL_ABROAD_DIALOG); 528 fragment.show(mActivity.getFragmentManager(), 529 SupportPhoneDialogFragment.TAG); 530 break; 531 } 532 } 533 } 534 } 535 } 536 537 private final class OfflineSpinnerItemSelectListener 538 implements AdapterView.OnItemSelectedListener { 539 540 @Override 541 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 542 final List<String> countryCodes = mSupportFeatureProvider.getPhoneSupportCountryCodes(); 543 final String selectedCountry = countryCodes.get(position); 544 if (!TextUtils.equals(selectedCountry, mSelectedCountry)) { 545 mSelectedCountry = selectedCountry; 546 refreshEscalationCards(); 547 } 548 } 549 550 @Override 551 public void onNothingSelected(AdapterView<?> parent) { 552 // Do nothing. 553 } 554 } 555 556 private final class OnlineSpinnerItemSelectListener 557 implements AdapterView.OnItemSelectedListener { 558 559 @Override 560 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 561 if (position == mAccounts.length) { 562 mActivity.startActivity(mSupportFeatureProvider.getAccountLoginIntent()); 563 // Make sure "Add account" is not shown as selected item 564 parent.setSelection(mSelectedAccountIndex); 565 } else if (position != mSelectedAccountIndex) { 566 mSelectedAccountIndex = position; 567 } 568 } 569 570 @Override 571 public void onNothingSelected(AdapterView<?> parent) { 572 // Do nothing. 573 } 574 } 575 576 /** 577 * {@link RecyclerView.ViewHolder} for support items. 578 */ 579 static final class ViewHolder extends RecyclerView.ViewHolder { 580 581 final ImageView iconView; 582 final TextView tileTitleView; 583 final TextView tileSummaryView; 584 final TextView text1View; 585 final TextView text2View; 586 final TextView summary1View; 587 final TextView summary2View; 588 589 ViewHolder(View itemView) { 590 super(itemView); 591 iconView = (ImageView) itemView.findViewById(android.R.id.icon); 592 tileTitleView = (TextView) itemView.findViewById(R.id.tile_title); 593 tileSummaryView = (TextView) itemView.findViewById(R.id.tile_summary); 594 text1View = (TextView) itemView.findViewById(android.R.id.text1); 595 text2View = (TextView) itemView.findViewById(android.R.id.text2); 596 summary1View = (TextView) itemView.findViewById(R.id.summary1); 597 summary2View = (TextView) itemView.findViewById(R.id.summary2); 598 } 599 } 600 601 /** 602 * Data for a single support item. 603 */ 604 @VisibleForTesting 605 static class SupportData { 606 607 final Intent intent; 608 final int metricsEvent; 609 @LayoutRes 610 final int type; 611 @DrawableRes 612 final int icon; 613 @StringRes 614 final int tileTitle; 615 final CharSequence tileTitleDescription; 616 final CharSequence tileSummary; 617 618 619 private SupportData(Builder builder) { 620 this.type = builder.mType; 621 this.icon = builder.mIcon; 622 this.tileTitle = builder.mTileTitle; 623 this.tileTitleDescription = builder.mTileTitleDescription; 624 this.tileSummary = builder.mTileSummary; 625 this.intent = builder.mIntent; 626 this.metricsEvent = builder.mMetricsEvent; 627 } 628 629 static class Builder { 630 631 protected final Context mContext; 632 @LayoutRes 633 private final int mType; 634 @DrawableRes 635 private int mIcon; 636 @StringRes 637 private int mTileTitle; 638 private CharSequence mTileTitleDescription; 639 private CharSequence mTileSummary; 640 private Intent mIntent; 641 private int mMetricsEvent = -1; 642 643 Builder(Context context, @LayoutRes int type) { 644 mContext = context; 645 mType = type; 646 } 647 648 Builder setIcon(@DrawableRes int icon) { 649 mIcon = icon; 650 return this; 651 } 652 653 Builder setTileTitle(@StringRes int title) { 654 mTileTitle = title; 655 return this; 656 } 657 658 Builder setTileTitleDescription(@StringRes int titleDescription) { 659 mTileTitleDescription = mContext.getString(titleDescription); 660 return this; 661 } 662 663 Builder setTileSummary(@StringRes int summary) { 664 mTileSummary = mContext.getString(summary); 665 return this; 666 } 667 668 Builder setTileSummary(CharSequence summary) { 669 mTileSummary = summary; 670 return this; 671 } 672 673 Builder setMetricsEvent(int metricsEvent) { 674 mMetricsEvent = metricsEvent; 675 return this; 676 } 677 678 Builder setIntent(Intent intent) { 679 mIntent = intent; 680 return this; 681 } 682 683 SupportData build() { 684 return new SupportData(this); 685 } 686 } 687 } 688 689 /** 690 * Data model for escalation cards. 691 */ 692 @VisibleForTesting 693 static class EscalationData extends SupportData { 694 695 @StringRes 696 final int text1; 697 final CharSequence text2; 698 final boolean enabled1; 699 final boolean enabled2; 700 final CharSequence summary1; 701 final CharSequence summary2; 702 703 private EscalationData(Builder builder) { 704 super(builder); 705 this.text1 = builder.mText1; 706 this.text2 = builder.mText2; 707 this.summary1 = builder.mSummary1; 708 this.summary2 = builder.mSummary2; 709 this.enabled1 = builder.mEnabled1; 710 this.enabled2 = builder.mEnabled2; 711 } 712 713 static class Builder extends SupportData.Builder { 714 715 @StringRes 716 private int mText1; 717 private CharSequence mText2; 718 private CharSequence mSummary1; 719 private CharSequence mSummary2; 720 private boolean mEnabled1; 721 private boolean mEnabled2; 722 723 protected Builder(Context context, @LayoutRes int type) { 724 super(context, type); 725 } 726 727 Builder(Context context) { 728 this(context, TYPE_ESCALATION_OPTIONS); 729 } 730 731 Builder setEnabled1(boolean enabled) { 732 mEnabled1 = enabled; 733 return this; 734 } 735 736 Builder setText1(@StringRes int text1) { 737 mText1 = text1; 738 return this; 739 } 740 741 Builder setText2(@StringRes int text2) { 742 mText2 = mContext.getString(text2); 743 return this; 744 } 745 746 Builder setText2(CharSequence text2) { 747 mText2 = text2; 748 return this; 749 } 750 751 Builder setSummary1(String summary1) { 752 mSummary1 = summary1; 753 return this; 754 } 755 756 Builder setEnabled2(boolean enabled) { 757 mEnabled2 = enabled; 758 return this; 759 } 760 761 Builder setSummary2(String summary2) { 762 mSummary2 = summary2; 763 return this; 764 } 765 766 EscalationData build() { 767 return new EscalationData(this); 768 } 769 } 770 } 771 772 /** 773 * Support data for offline mode. 774 */ 775 private static final class OfflineEscalationData extends EscalationData { 776 777 final List<String> countries; 778 final SupportPhone tollFreePhone; 779 final SupportPhone tolledPhone; 780 781 private OfflineEscalationData(Builder builder) { 782 super(builder); 783 countries = builder.mCountries; 784 tollFreePhone = builder.mTollFreePhone; 785 tolledPhone = builder.mTolledPhone; 786 } 787 788 static final class Builder extends EscalationData.Builder { 789 790 private List<String> mCountries; 791 private SupportPhone mTollFreePhone; 792 private SupportPhone mTolledPhone; 793 794 Builder(Context context) { 795 super(context, TYPE_ESCALATION_OPTIONS_OFFLINE); 796 } 797 798 Builder setCountries(List<String> countries) { 799 mCountries = countries; 800 return this; 801 } 802 803 Builder setTollFreePhone(SupportPhone phone) { 804 mTollFreePhone = phone; 805 return this; 806 } 807 808 Builder setTolledPhone(SupportPhone phone) { 809 mTolledPhone = phone; 810 return this; 811 } 812 813 OfflineEscalationData build() { 814 return new OfflineEscalationData(this); 815 } 816 } 817 } 818 819 @VisibleForTesting 820 List<SupportData> getSupportData() { 821 return mSupportData; 822 } 823 } 824