When offline show a list of countries with phone support.

Bug: 29105266

TODO:
- Support phones UI for international travel
- Selecting default country
- Show operation hours for selected country

Change-Id: I08a6780497dfcb9b9dc8670f2705d01df021c57b
This commit is contained in:
Fan Zhang
2016-06-23 11:04:21 -07:00
parent c3a0a18f77
commit a44b1efbdc
5 changed files with 293 additions and 25 deletions

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background_grey"
android:gravity="center_horizontal"
android:paddingBottom="40dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/support_country_list_title"
android:textAppearance="@style/TextAppearance.Small"
android:textColor="?android:attr/textColorSecondary"/>
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<Button
android:id="@android:id/text1"
style="@style/SupportPrimaryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"/>
<Button
android:id="@android:id/text2"
style="@style/SupportSecondaryButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:minHeight="48dp"/>
</LinearLayout>

View File

@@ -7522,12 +7522,24 @@
<xliff:g id="start_day">%s</xliff:g> - <xliff:g id="end_day">%s</xliff:g>, <xliff:g id="start_time">%s</xliff:g> - <xliff:g id="end_time">%s</xliff:g>&lt;br&gt;
</string>
<!-- Button label for choosing country for phone support. [CHAR LIMIT=40]-->
<string name="support_country_list_title">Support for:</string>
<!-- Template for formatting country and language. eg Canada - French [CHAR LIMIT=NONE]-->
<string name="support_country_format"><xliff:g id="country" example="Canada">%s</xliff:g> - <xliff:g id="language" example="French">%s</xliff:g></string>
<!-- Title text that indicates there is not internet connection. [CHAR LIMIT=80]-->
<string name="support_offline_title">You\'re offline</string>
<!-- Summary text telling user to connect to Internet in order to request customer support. [CHAR LIMIT=NONE]-->
<string name="support_offline_summary">To reach support, first connect to Wi-Fi or data.</string>
<!-- Title text for a list of international support phone numbers. [CHAR LIMIT=60]-->
<string name="support_international_phone_title">Traveling aboard?</string>
<!-- Description text warning international phone charge may apply when dialing support numbers. [CHAR LIMIT=NONE]-->
<string name="support_international_phone_summary">International charges may apply</string>
<!-- Button label for contacting customer support by phone [CHAR LIMIT=20]-->
<string name="support_escalation_by_phone">Phone</string>

View File

@@ -28,7 +28,10 @@ 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;
@@ -36,6 +39,7 @@ 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 java.util.ArrayList;
import java.util.List;
@@ -51,15 +55,19 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
private static final int TYPE_TITLE = R.layout.support_item_title;
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_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;
@@ -69,6 +77,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
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;
@@ -92,6 +101,9 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
case TYPE_ESCALATION_OPTIONS:
bindEscalationOptions(holder, data);
break;
case TYPE_ESCALATION_OPTIONS_OFFLINE:
bindOfflineEscalationOptions(holder, (OfflineSupportData) data);
break;
default:
bindSupportTile(holder, data);
break;
@@ -145,15 +157,16 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
mSupportData.clear();
if (mAccount == null) {
addSignInPromo();
} else {
} else if (mHasInternet) {
addEscalationCards();
} else {
addOfflineEscalationCards();
}
addMoreHelpItems();
notifyDataSetChanged();
}
private void addEscalationCards() {
if (mHasInternet) {
if (mSupportFeatureProvider.isAlwaysOperating(PHONE)
|| mSupportFeatureProvider.isAlwaysOperating(CHAT)) {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_TITLE)
@@ -172,12 +185,6 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
.setText2(mSupportFeatureProvider.getOperationHours(mActivity, PHONE))
.build());
}
} else {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_TITLE)
.setText1(R.string.support_offline_title)
.setText2(R.string.support_offline_summary)
.build());
}
final SupportData.Builder builder =
new SupportData.Builder(mActivity, TYPE_ESCALATION_OPTIONS);
if (mSupportFeatureProvider.isSupportTypeEnabled(mActivity, PHONE)) {
@@ -193,6 +200,20 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
mSupportData.add(builder.build());
}
private void addOfflineEscalationCards() {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_TITLE)
.setText1(R.string.support_offline_title)
.setText2(R.string.support_offline_summary)
.build());
final OfflineSupportData.Builder builder = new OfflineSupportData.Builder(mActivity);
builder.setCountries(mSupportFeatureProvider.getPhoneSupportCountries())
.setTollFreePhone(mSupportFeatureProvider.getSupportPhones(
mSelectedCountry, true /* isTollFree */))
.setTolledPhone(mSupportFeatureProvider.getSupportPhones(
mSelectedCountry, false /* isTollFree */));
mSupportData.add(builder.build());
}
private void addSignInPromo() {
mSupportData.add(new SupportData.Builder(mActivity, TYPE_TITLE)
.setText1(R.string.support_sign_in_required_title)
@@ -246,6 +267,38 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
}
}
private void bindOfflineEscalationOptions(ViewHolder holder, OfflineSupportData data) {
// 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, SupportData data) {
holder.text1View.setText(data.text1);
holder.text2View.setText(data.text2);
@@ -299,7 +352,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
0 /* requestCode */);
break;
}
} else {
} else if (mHasInternet) {
switch (v.getId()) {
case android.R.id.text1:
MetricsLogger.action(mActivity,
@@ -312,7 +365,37 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
tryStartDisclaimerAndSupport(CHAT);
break;
}
} else {
switch (v.getId()) {
case android.R.id.text1:
final SupportPhone phone = mSupportFeatureProvider
.getSupportPhones(mSelectedCountry, true /* isTollFree */);
if (phone != null) {
mActivity.startActivity(phone.getDialIntent());
}
break;
case android.R.id.text2:
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;
refreshData();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing.
}
}
@@ -340,7 +423,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
/**
* Data for a single support item.
*/
private static final class SupportData {
private static class SupportData {
final Intent intent;
final int metricsEvent;
@@ -369,7 +452,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
this.metricsEvent = builder.mMetricsEvent;
}
static final class Builder {
static class Builder {
private final Context mContext;
@LayoutRes
@@ -446,4 +529,51 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
}
}
}
/**
* Support data for offline mode.
*/
private static final class OfflineSupportData extends SupportData {
final List<String> countries;
final SupportPhone tollFreePhone;
final SupportPhone tolledPhone;
private OfflineSupportData(Builder builder) {
super(builder);
countries = builder.mCountries;
tollFreePhone = builder.mTollFreePhone;
tolledPhone = builder.mTolledPhone;
}
static final class Builder extends SupportData.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;
}
OfflineSupportData build() {
return new OfflineSupportData(this);
}
}
}
}

View File

@@ -22,8 +22,11 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import com.android.settings.support.SupportPhone;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Feature provider for support tab.
@@ -68,6 +71,22 @@ public interface SupportFeatureProvider {
*/
String getEstimatedWaitTime(Context context, @SupportType int type);
/**
* Returns a list of country codes that have phone support.
*/
List<String> getPhoneSupportCountryCodes();
/**
* Returns a list of countries that have phone support.
*/
List<String> getPhoneSupportCountries();
/**
* Returns a support phone for specified country.
*/
SupportPhone getSupportPhones(String countryCode, boolean isTollfree);
/**
* Whether or not a disclaimer dialog should be displayed.
*/

View File

@@ -0,0 +1,52 @@
/*
* 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.support;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import java.text.ParseException;
/**
* Data model for a support phone number.
*/
public final class SupportPhone {
public final String language;
public final String number;
public final boolean isTollFree;
public SupportPhone(String config) throws ParseException {
// Config follows this format: language:[tollfree|tolled]:number
final String[] tokens = config.split(":");
if (tokens.length != 3) {
throw new ParseException("Phone config is invalid " + config, 0);
}
language = tokens[0];
isTollFree = TextUtils.equals(tokens[1], "tollfree");
number = tokens[2];
}
public Intent getDialIntent() {
return new Intent(Intent.ACTION_DIAL)
.setData(new Uri.Builder()
.scheme("tel")
.appendPath(number)
.build());
}
}