Files
app_Settings/src/com/android/settings/dashboard/SupportItemAdapter.java
Doris Ling 0d4b8a3b19 Fix crash when support item is clicked.
Check for existing activity to handle the support item intent before
adding it to support tile or trying to launch the activity.

Change-Id: I42a733c2d862b303773aeee1ba3b21b718e59066
Fixes: 29118295
2016-07-25 14:06:42 -07:00

734 lines
28 KiB
Java

/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.dashboard;
import android.accounts.Account;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
import android.annotation.StringRes;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto;
import com.android.settings.R;
import com.android.settings.overlay.SupportFeatureProvider;
import com.android.settings.support.SupportDisclaimerDialogFragment;
import com.android.settings.support.SupportPhone;
import com.android.settings.support.SupportPhoneDialogFragment;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.android.settings.overlay.SupportFeatureProvider.SupportType.CHAT;
import static com.android.settings.overlay.SupportFeatureProvider.SupportType.PHONE;
/**
* Item adapter for support tiles.
*/
public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAdapter.ViewHolder> {
private static final String STATE_SELECTED_COUNTRY = "STATE_SELECTED_COUNTRY";
private static final int TYPE_ESCALATION_OPTIONS = R.layout.support_escalation_options;
private static final int TYPE_ESCALATION_OPTIONS_OFFLINE =
R.layout.support_offline_escalation_options;
private static final int TYPE_SUPPORT_TILE = R.layout.support_tile;
private static final int TYPE_SUPPORT_TILE_SPACER = R.layout.support_tile_spacer;
private static final int TYPE_SIGN_IN_BUTTON = R.layout.support_sign_in_button;
private final Activity mActivity;
private final EscalationClickListener mEscalationClickListener;
private final SpinnerItemSelectListener mSpinnerItemSelectListener;
private final SupportFeatureProvider mSupportFeatureProvider;
private final View.OnClickListener mItemClickListener;
private final List<SupportData> mSupportData;
private String mSelectedCountry;
private boolean mHasInternet;
private Account mAccount;
public SupportItemAdapter(Activity activity, Bundle savedInstanceState,
SupportFeatureProvider supportFeatureProvider, View.OnClickListener itemClickListener) {
mActivity = activity;
mSupportFeatureProvider = supportFeatureProvider;
mItemClickListener = itemClickListener;
mEscalationClickListener = new EscalationClickListener();
mSpinnerItemSelectListener = new SpinnerItemSelectListener();
mSupportData = new ArrayList<>();
// Optimistically assume we have Internet access. It will be updated later to correct value.
mHasInternet = true;
if (savedInstanceState != null) {
mSelectedCountry = savedInstanceState.getString(STATE_SELECTED_COUNTRY);
} else {
mSelectedCountry = mSupportFeatureProvider.getCurrentCountryCodeIfHasConfig(PHONE);
}
mAccount = mSupportFeatureProvider.getSupportEligibleAccount(mActivity);
refreshData();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
viewType, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final SupportData data = mSupportData.get(position);
switch (holder.getItemViewType()) {
case TYPE_SIGN_IN_BUTTON:
bindSignInPromoTile(holder, (EscalationData) data);
break;
case TYPE_ESCALATION_OPTIONS:
bindEscalationOptions(holder, (EscalationData) data);
break;
case TYPE_ESCALATION_OPTIONS_OFFLINE:
bindOfflineEscalationOptions(holder, (OfflineEscalationData) data);
break;
case TYPE_SUPPORT_TILE_SPACER:
break;
default:
bindSupportTile(holder, data);
break;
}
}
@Override
public int getItemViewType(int position) {
return mSupportData.get(position).type;
}
@Override
public int getItemCount() {
return mSupportData.size();
}
/**
* Called when a support item is clicked.
*/
public void onItemClicked(int position) {
if (position >= 0 && position < mSupportData.size()) {
final SupportData data = mSupportData.get(position);
if (data.intent != null &&
mActivity.getPackageManager().resolveActivity(data.intent, 0) != null) {
if (data.metricsEvent >= 0) {
MetricsLogger.action(mActivity, data.metricsEvent);
}
mActivity.startActivityForResult(data.intent, 0);
}
}
}
public void setHasInternet(boolean hasInternet) {
if (mHasInternet != hasInternet) {
mHasInternet = hasInternet;
refreshEscalationCards();
}
}
public void setAccount(Account account) {
if (!Objects.equals(mAccount, account)) {
mAccount = account;
mSupportFeatureProvider.refreshOperationRules();
refreshEscalationCards();
}
}
public void onSaveInstanceState(Bundle outState) {
outState.putString(STATE_SELECTED_COUNTRY, mSelectedCountry);
}
/**
* Create data for the adapter. If there is already data in the adapter, they will be
* destroyed and recreated.
*/
private void refreshData() {
mSupportData.clear();
addEscalationCards();
addMoreHelpItems();
notifyDataSetChanged();
}
/**
* Adds 1 escalation card. Based on current phone state, the escalation card can display
* different content.
*/
private void addEscalationCards() {
if (mAccount == null) {
addSignInPromo();
} else if (mHasInternet) {
addOnlineEscalationCards();
} else {
addOfflineEscalationCards();
}
}
/**
* Finds and refreshes escalation card data.
*/
private void refreshEscalationCards() {
if (getItemCount() > 0) {
final int itemType = getItemViewType(0 /* position */);
if (itemType == TYPE_SIGN_IN_BUTTON
|| itemType == TYPE_ESCALATION_OPTIONS
|| itemType == TYPE_ESCALATION_OPTIONS_OFFLINE) {
mSupportData.remove(0 /* position */);
addEscalationCards();
notifyItemChanged(0 /* position */);
}
}
}
private void addOnlineEscalationCards() {
final boolean hasPhoneOperation =
mSupportFeatureProvider.isSupportTypeEnabled(mActivity, PHONE);
final boolean hasChatOperation =
mSupportFeatureProvider.isSupportTypeEnabled(mActivity, CHAT);
final EscalationData.Builder builder = new EscalationData.Builder(mActivity);
if (!hasPhoneOperation && !hasChatOperation) {
// No support at all.
builder.setTileTitle(R.string.support_escalation_title)
.setTileSummary(R.string.support_escalation_unavailable_summary);
} else if (mSupportFeatureProvider.isAlwaysOperating(PHONE, null /* countryCode */)
|| mSupportFeatureProvider.isAlwaysOperating(CHAT, null /* countryCode */)) {
// Support is available.
builder.setTileTitle(R.string.support_escalation_24_7_title)
.setTileTitleDescription(R.string.support_escalation_24_7_content_description)
.setTileSummary(mActivity.getString(R.string.support_escalation_24_7_summary));
} else if (mSupportFeatureProvider.isOperatingNow(PHONE)
|| mSupportFeatureProvider.isOperatingNow(CHAT)) {
// Support is available now.
builder.setTileTitle(R.string.support_escalation_title)
.setTileSummary(R.string.support_escalation_summary);
} else {
// Support is now temporarily unavailable.
builder.setTileTitle(R.string.support_escalation_title)
.setTileSummary(
mSupportFeatureProvider.getOperationHours(mActivity, PHONE, null,
true /* hasInternet */));
}
if (hasPhoneOperation) {
builder.setText1(R.string.support_escalation_by_phone)
.setSummary1(mSupportFeatureProvider.getEstimatedWaitTime(mActivity, PHONE))
.setEnabled1(mSupportFeatureProvider.isOperatingNow(PHONE));
}
if (hasChatOperation) {
builder.setText2(R.string.support_escalation_by_chat)
.setSummary2(mSupportFeatureProvider.getEstimatedWaitTime(mActivity, CHAT))
.setEnabled2(mSupportFeatureProvider.isOperatingNow(CHAT));
}
mSupportData.add(0 /* index */, builder.build());
}
private void addOfflineEscalationCards() {
final CharSequence operatingHours;
final boolean isPhoneSupportAlwaysOperating =
mSupportFeatureProvider.isAlwaysOperating(PHONE, mSelectedCountry);
if (isPhoneSupportAlwaysOperating) {
operatingHours = mActivity.getString(R.string.support_escalation_24_7_summary);
} else {
operatingHours = mSupportFeatureProvider.getOperationHours(mActivity,
PHONE, mSelectedCountry, false /* hasInternet */);
}
mSupportData.add(0 /* index */, new OfflineEscalationData.Builder(mActivity)
.setCountries(mSupportFeatureProvider.getPhoneSupportCountries())
.setTollFreePhone(mSupportFeatureProvider.getSupportPhones(
mSelectedCountry, true /* isTollFree */))
.setTolledPhone(mSupportFeatureProvider.getSupportPhones(
mSelectedCountry, false /* isTollFree */))
.setTileTitle(isPhoneSupportAlwaysOperating
? R.string.support_escalation_24_7_title
: R.string.support_escalation_title)
.setTileTitleDescription(isPhoneSupportAlwaysOperating
? R.string.support_escalation_24_7_content_description
: R.string.support_escalation_title)
.setTileSummary(operatingHours)
.build());
}
private void addSignInPromo() {
mSupportData.add(0 /* index */, new EscalationData.Builder(mActivity, TYPE_SIGN_IN_BUTTON)
.setText1(R.string.support_sign_in_button_text)
.setText2(R.string.support_sign_in_required_help)
.setTileTitle(R.string.support_sign_in_required_title)
.setTileSummary(R.string.support_sign_in_required_summary)
.build());
}
private void addMoreHelpItems() {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_SUPPORT_TILE_SPACER).build());
PackageManager packageManager = mActivity.getPackageManager();
Intent intent = mSupportFeatureProvider.getHelpIntent(mActivity);
if (packageManager.resolveActivity(intent, 0) != null) {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_SUPPORT_TILE)
.setIcon(R.drawable.ic_help_24dp)
.setTileTitle(R.string.support_help_feedback_title)
.setIntent(intent)
.setMetricsEvent(MetricsProto.MetricsEvent.ACTION_SUPPORT_HELP_AND_FEEDBACK)
.build());
}
intent = mSupportFeatureProvider.getTipsAndTricksIntent(mActivity);
if (packageManager.resolveActivity(intent, 0) != null) {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_SUPPORT_TILE)
.setIcon(R.drawable.ic_lightbulb_outline_24)
.setTileTitle(R.string.support_tips_and_tricks_title)
.setIntent(intent)
.setMetricsEvent(MetricsProto.MetricsEvent.ACTION_SUPPORT_TIPS_AND_TRICKS)
.build());
}
}
private void bindEscalationOptions(ViewHolder holder, EscalationData data) {
holder.tileTitleView.setText(data.tileTitle);
holder.tileTitleView.setContentDescription(data.tileTitleDescription);
holder.tileSummaryView.setText(data.tileSummary);
if (data.text1 == 0) {
holder.text1View.setVisibility(View.GONE);
} else {
holder.text1View.setText(data.text1);
holder.text1View.setOnClickListener(mEscalationClickListener);
holder.text1View.setEnabled(data.enabled1 && mHasInternet);
holder.text1View.setVisibility(View.VISIBLE);
}
if (TextUtils.isEmpty(data.text2)) {
holder.text2View.setVisibility(View.GONE);
} else {
holder.text2View.setText(data.text2);
holder.text2View.setOnClickListener(mEscalationClickListener);
holder.text2View.setEnabled(data.enabled2 && mHasInternet);
holder.text2View.setVisibility(View.VISIBLE);
}
if (holder.summary1View != null) {
holder.summary1View.setText(data.summary1);
holder.summary1View.setVisibility(mHasInternet && !TextUtils.isEmpty(data.summary1)
? View.VISIBLE : View.GONE);
}
if (holder.summary2View != null) {
holder.summary2View.setText(data.summary2);
holder.summary2View.setVisibility(mHasInternet && !TextUtils.isEmpty(data.summary2)
? View.VISIBLE : View.GONE);
}
}
private void bindOfflineEscalationOptions(ViewHolder holder, OfflineEscalationData data) {
// Bind Title
holder.tileTitleView.setText(data.tileTitle);
holder.tileTitleView.setContentDescription(data.tileTitleDescription);
holder.tileSummaryView.setText(data.tileSummary);
// Bind spinner
final Spinner spinner = (Spinner) holder.itemView.findViewById(R.id.spinner);
final ArrayAdapter<String> adapter = new ArrayAdapter(
mActivity, android.R.layout.simple_spinner_dropdown_item, data.countries);
spinner.setAdapter(adapter);
final List<String> countryCodes = mSupportFeatureProvider.getPhoneSupportCountryCodes();
for (int i = 0; i < countryCodes.size(); i++) {
if (TextUtils.equals(countryCodes.get(i), mSelectedCountry)) {
spinner.setSelection(i);
break;
}
}
spinner.setOnItemSelectedListener(mSpinnerItemSelectListener);
// Bind buttons
if (data.tollFreePhone != null) {
holder.text1View.setText(data.tollFreePhone.number);
holder.text1View.setVisibility(View.VISIBLE);
holder.text1View.setOnClickListener(mEscalationClickListener);
} else {
holder.text1View.setVisibility(View.GONE);
}
if (data.tolledPhone != null) {
holder.text2View.setText(
mActivity.getString(R.string.support_international_phone_title));
holder.text2View.setVisibility(View.VISIBLE);
holder.text2View.setOnClickListener(mEscalationClickListener);
} else {
holder.text2View.setVisibility(View.GONE);
}
}
private void bindSignInPromoTile(ViewHolder holder, EscalationData data) {
holder.tileTitleView.setText(data.tileTitle);
holder.tileTitleView.setContentDescription(data.tileTitleDescription);
holder.tileSummaryView.setText(data.tileSummary);
holder.text1View.setText(data.text1);
holder.text2View.setText(data.text2);
holder.text1View.setOnClickListener(mEscalationClickListener);
holder.text2View.setOnClickListener(mEscalationClickListener);
}
private void bindSupportTile(ViewHolder holder, SupportData data) {
if (holder.iconView != null) {
holder.iconView.setImageResource(data.icon);
}
if (holder.tileTitleView != null) {
holder.tileTitleView.setText(data.tileTitle);
holder.tileTitleView.setContentDescription(data.tileTitleDescription);
}
if (holder.tileSummaryView != null) {
holder.tileSummaryView.setText(data.tileSummary);
}
holder.itemView.setOnClickListener(mItemClickListener);
}
/**
* Show a disclaimer dialog and start support action after disclaimer has been acknowledged.
*/
private void tryStartDisclaimerAndSupport(final @SupportFeatureProvider.SupportType int type) {
if (mSupportFeatureProvider.shouldShowDisclaimerDialog(mActivity)) {
DialogFragment fragment = SupportDisclaimerDialogFragment.newInstance(mAccount, type);
fragment.show(mActivity.getFragmentManager(), SupportDisclaimerDialogFragment.TAG);
return;
}
mSupportFeatureProvider.startSupport(mActivity, mAccount, type);
}
/**
* Click handler for starting escalation options.
*/
private final class EscalationClickListener implements View.OnClickListener {
@Override
public void onClick(final View v) {
if (mAccount == null) {
switch (v.getId()) {
case android.R.id.text1:
MetricsLogger.action(mActivity,
MetricsProto.MetricsEvent.ACTION_SUPPORT_SIGN_IN);
mActivity.startActivityForResult(
mSupportFeatureProvider.getAccountLoginIntent(),
0 /* requestCode */);
break;
case android.R.id.text2:
mActivity.startActivityForResult(
mSupportFeatureProvider.getSignInHelpIntent(mActivity),
0 /* requestCode */);
break;
}
} else if (mHasInternet) {
switch (v.getId()) {
case android.R.id.text1:
MetricsLogger.action(mActivity,
MetricsProto.MetricsEvent.ACTION_SUPPORT_PHONE);
tryStartDisclaimerAndSupport(PHONE);
break;
case android.R.id.text2:
MetricsLogger.action(mActivity,
MetricsProto.MetricsEvent.ACTION_SUPPORT_CHAT);
tryStartDisclaimerAndSupport(CHAT);
break;
}
} else {
switch (v.getId()) {
case android.R.id.text1: {
final SupportPhone phone = mSupportFeatureProvider
.getSupportPhones(mSelectedCountry, true /* isTollFree */);
if (phone != null) {
MetricsLogger.action(mActivity,
MetricsProto.MetricsEvent.ACTION_SUPPORT_DAIL_TOLLFREE);
mActivity.startActivity(phone.getDialIntent());
}
break;
}
case android.R.id.text2: {
final SupportPhone phone = mSupportFeatureProvider
.getSupportPhones(mSelectedCountry, false /* isTollFree */);
final SupportPhoneDialogFragment fragment =
SupportPhoneDialogFragment.newInstance(phone);
MetricsLogger.action(mActivity,
MetricsProto.MetricsEvent.ACTION_SUPPORT_VIEW_TRAVEL_ABROAD_DIALOG);
fragment.show(mActivity.getFragmentManager(),
SupportPhoneDialogFragment.TAG);
break;
}
}
}
}
}
private final class SpinnerItemSelectListener implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
final List<String> countryCodes = mSupportFeatureProvider.getPhoneSupportCountryCodes();
final String selectedCountry = countryCodes.get(position);
if (!TextUtils.equals(selectedCountry, mSelectedCountry)) {
mSelectedCountry = selectedCountry;
refreshEscalationCards();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing.
}
}
/**
* {@link RecyclerView.ViewHolder} for support items.
*/
static final class ViewHolder extends RecyclerView.ViewHolder {
final ImageView iconView;
final TextView tileTitleView;
final TextView tileSummaryView;
final TextView text1View;
final TextView text2View;
final TextView summary1View;
final TextView summary2View;
ViewHolder(View itemView) {
super(itemView);
iconView = (ImageView) itemView.findViewById(android.R.id.icon);
tileTitleView = (TextView) itemView.findViewById(R.id.tile_title);
tileSummaryView = (TextView) itemView.findViewById(R.id.tile_summary);
text1View = (TextView) itemView.findViewById(android.R.id.text1);
text2View = (TextView) itemView.findViewById(android.R.id.text2);
summary1View = (TextView) itemView.findViewById(R.id.summary1);
summary2View = (TextView) itemView.findViewById(R.id.summary2);
}
}
/**
* Data for a single support item.
*/
private static class SupportData {
final Intent intent;
final int metricsEvent;
@LayoutRes
final int type;
@DrawableRes
final int icon;
@StringRes
final int tileTitle;
final CharSequence tileTitleDescription;
final CharSequence tileSummary;
private SupportData(Builder builder) {
this.type = builder.mType;
this.icon = builder.mIcon;
this.tileTitle = builder.mTileTitle;
this.tileTitleDescription = builder.mTileTitleDescription;
this.tileSummary = builder.mTileSummary;
this.intent = builder.mIntent;
this.metricsEvent = builder.mMetricsEvent;
}
static class Builder {
protected final Context mContext;
@LayoutRes
private final int mType;
@DrawableRes
private int mIcon;
@StringRes
private int mTileTitle;
private CharSequence mTileTitleDescription;
private CharSequence mTileSummary;
private Intent mIntent;
private int mMetricsEvent = -1;
Builder(Context context, @LayoutRes int type) {
mContext = context;
mType = type;
}
Builder setIcon(@DrawableRes int icon) {
mIcon = icon;
return this;
}
Builder setTileTitle(@StringRes int title) {
mTileTitle = title;
return this;
}
Builder setTileTitleDescription(@StringRes int titleDescription) {
mTileTitleDescription = mContext.getString(titleDescription);
return this;
}
Builder setTileSummary(@StringRes int summary) {
mTileSummary = mContext.getString(summary);
return this;
}
Builder setTileSummary(CharSequence summary) {
mTileSummary = summary;
return this;
}
Builder setMetricsEvent(int metricsEvent) {
mMetricsEvent = metricsEvent;
return this;
}
Builder setIntent(Intent intent) {
mIntent = intent;
return this;
}
SupportData build() {
return new SupportData(this);
}
}
}
/**
* Data model for escalation cards.
*/
private static class EscalationData extends SupportData {
@StringRes
final int text1;
final CharSequence text2;
final boolean enabled1;
final boolean enabled2;
final CharSequence summary1;
final CharSequence summary2;
private EscalationData(Builder builder) {
super(builder);
this.text1 = builder.mText1;
this.text2 = builder.mText2;
this.summary1 = builder.mSummary1;
this.summary2 = builder.mSummary2;
this.enabled1 = builder.mEnabled1;
this.enabled2 = builder.mEnabled2;
}
static class Builder extends SupportData.Builder {
@StringRes
private int mText1;
private CharSequence mText2;
private CharSequence mSummary1;
private CharSequence mSummary2;
private boolean mEnabled1;
private boolean mEnabled2;
protected Builder(Context context, @LayoutRes int type) {
super(context, type);
}
Builder(Context context) {
this(context, TYPE_ESCALATION_OPTIONS);
}
Builder setEnabled1(boolean enabled) {
mEnabled1 = enabled;
return this;
}
Builder setText1(@StringRes int text1) {
mText1 = text1;
return this;
}
Builder setText2(@StringRes int text2) {
mText2 = mContext.getString(text2);
return this;
}
Builder setText2(CharSequence text2) {
mText2 = text2;
return this;
}
Builder setSummary1(String summary1) {
mSummary1 = summary1;
return this;
}
Builder setEnabled2(boolean enabled) {
mEnabled2 = enabled;
return this;
}
Builder setSummary2(String summary2) {
mSummary2 = summary2;
return this;
}
EscalationData build() {
return new EscalationData(this);
}
}
}
/**
* Support data for offline mode.
*/
private static final class OfflineEscalationData extends EscalationData {
final List<String> countries;
final SupportPhone tollFreePhone;
final SupportPhone tolledPhone;
private OfflineEscalationData(Builder builder) {
super(builder);
countries = builder.mCountries;
tollFreePhone = builder.mTollFreePhone;
tolledPhone = builder.mTolledPhone;
}
static final class Builder extends EscalationData.Builder {
private List<String> mCountries;
private SupportPhone mTollFreePhone;
private SupportPhone mTolledPhone;
Builder(Context context) {
super(context, TYPE_ESCALATION_OPTIONS_OFFLINE);
}
Builder setCountries(List<String> countries) {
mCountries = countries;
return this;
}
Builder setTollFreePhone(SupportPhone phone) {
mTollFreePhone = phone;
return this;
}
Builder setTolledPhone(SupportPhone phone) {
mTolledPhone = phone;
return this;
}
OfflineEscalationData build() {
return new OfflineEscalationData(this);
}
}
}
}