Add account picker to Support Tab

Add a spinner to select the account for user.

Bug: 32249920
Test: make RunSettingsRoboTests
Change-Id: I372d16ec5ec3230f5f2994d79f4fd27085092236
This commit is contained in:
jackqdyulei
2016-11-07 14:36:02 -08:00
parent 737ae83a66
commit 5538be509c
7 changed files with 270 additions and 25 deletions

View File

@@ -0,0 +1,26 @@
<?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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textAlignment="inherit"/>

View File

@@ -34,11 +34,25 @@
android:gravity="center_horizontal"
android:paddingTop="8dp"
android:paddingBottom="30dp"
android:textAppearance="@style/TextAppearance.Small"
android:textColor="?android:attr/textColorSecondary"/>
android:textAppearance="?android:attr/textAppearanceSmall"/>
<TextView
android:id="@+id/account_request_prefix"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/support_account_request_prefix"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<Spinner
android:id="@+id/account_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginStart="16dp"
android:gravity="center_horizontal"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<LinearLayout
@@ -60,8 +74,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:textAppearance="@style/TextAppearance.Small"
android:textColor="?android:attr/textColorSecondary"/>
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
@@ -82,8 +95,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:textAppearance="@style/TextAppearance.Small"
android:textColor="?android:attr/textColorSecondary"/>
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -7797,6 +7797,12 @@
<!-- Checkbox text, when checked dialog will not show again [CHAR LIMIT=80] -->
<string name="support_disclaimer_do_not_show">Do not show again</string>
<!-- Prefix text for the account picker, e.g. "Requesting as user@gmail.com" [CHAR LIMIT=60] -->
<string name="support_account_request_prefix">Requesting as</string>
<!-- Spinner dropdown text, when selected will try to add account [CHAR LIMIT=60] -->
<string name="support_account_picker_add_account">Add account</string>
<!-- [CHAR LIMIT=60] Title of work profile setting page -->
<string name="managed_profile_settings_title">Work profile settings</string>
<!-- [CHAR LIMIT=60] The preference title for enabling cross-profile remote contact search -->

View File

@@ -138,8 +138,8 @@ public final class SupportFragment extends InstrumentedFragment implements View.
@Override
public void onAccountsUpdated(Account[] accounts) {
// Account changed, update support items.
mSupportItemAdapter.setAccount(
mSupportFeatureProvider.getSupportEligibleAccount(mActivity));
mSupportItemAdapter.setAccounts(
mSupportFeatureProvider.getSupportEligibleAccounts(mActivity));
}
@Override

View File

@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -38,6 +39,7 @@ import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.util.ArrayUtils;
import com.android.settings.R;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
import com.android.settings.overlay.SupportFeatureProvider;
@@ -46,8 +48,8 @@ import com.android.settings.support.SupportPhone;
import com.android.settings.support.SupportPhoneDialogFragment;
import java.util.ArrayList;
import java.util.Arrays;
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;
@@ -58,6 +60,7 @@ import static com.android.settings.overlay.SupportFeatureProvider.SupportType.PH
public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAdapter.ViewHolder> {
private static final String STATE_SELECTED_COUNTRY = "STATE_SELECTED_COUNTRY";
private static final String ACCOUNT_SELECTED_INDEX = "ACCOUNT_SELECTED_INDEX";
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;
@@ -67,7 +70,8 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
private final Activity mActivity;
private final EscalationClickListener mEscalationClickListener;
private final SpinnerItemSelectListener mSpinnerItemSelectListener;
private final OfflineSpinnerItemSelectListener mOfflineSpinnerItemSelectListener;
private final OnlineSpinnerItemSelectListener mOnlineSpinnerItemSelectListener;
private final SupportFeatureProvider mSupportFeatureProvider;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final View.OnClickListener mItemClickListener;
@@ -75,7 +79,8 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
private String mSelectedCountry;
private boolean mHasInternet;
private Account mAccount;
private Account[] mAccounts;
private int mSelectedAccountIndex;
public SupportItemAdapter(Activity activity, Bundle savedInstanceState,
SupportFeatureProvider supportFeatureProvider,
@@ -86,16 +91,20 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
mMetricsFeatureProvider = metricsFeatureProvider;
mItemClickListener = itemClickListener;
mEscalationClickListener = new EscalationClickListener();
mSpinnerItemSelectListener = new SpinnerItemSelectListener();
mOfflineSpinnerItemSelectListener = new OfflineSpinnerItemSelectListener();
mOnlineSpinnerItemSelectListener = new OnlineSpinnerItemSelectListener();
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);
mSelectedAccountIndex = savedInstanceState.getInt(ACCOUNT_SELECTED_INDEX);
} else {
mSelectedCountry = mSupportFeatureProvider.getCurrentCountryCodeIfHasConfig(PHONE);
mSelectedAccountIndex = 0;
}
mAccount = mSupportFeatureProvider.getSupportEligibleAccount(mActivity);
mAccounts = mSupportFeatureProvider.getSupportEligibleAccounts(mActivity);
refreshData();
}
@@ -159,9 +168,11 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
}
}
public void setAccount(Account account) {
if (!Objects.equals(mAccount, account)) {
mAccount = account;
public void setAccounts(Account accounts[]) {
if (!Arrays.equals(mAccounts, accounts)) {
int index = ArrayUtils.indexOf(accounts, mAccounts[mSelectedAccountIndex]);
mSelectedAccountIndex = index != -1 ? index : 0;
mAccounts = accounts;
mSupportFeatureProvider.refreshOperationRules();
refreshEscalationCards();
}
@@ -169,6 +180,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
public void onSaveInstanceState(Bundle outState) {
outState.putString(STATE_SELECTED_COUNTRY, mSelectedCountry);
outState.putInt(ACCOUNT_SELECTED_INDEX, mSelectedAccountIndex);
}
/**
@@ -187,7 +199,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
* different content.
*/
private void addEscalationCards() {
if (mAccount == null) {
if (mAccounts.length == 0) {
addSignInPromo();
} else if (mHasInternet) {
addOnlineEscalationCards();
@@ -341,6 +353,21 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
holder.summary2View.setVisibility(mHasInternet && !TextUtils.isEmpty(data.summary2)
? View.VISIBLE : View.GONE);
}
bindAccountPicker(holder);
}
@VisibleForTesting
public void bindAccountPicker(ViewHolder holder) {
final Spinner spinner = (Spinner) holder.itemView.findViewById(R.id.account_spinner);
final ArrayAdapter<String> adapter = new ArrayAdapter(
mActivity, R.layout.support_account_spinner_item,
extractAccountNames(mAccounts));
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(mOnlineSpinnerItemSelectListener);
spinner.setSelection(mSelectedAccountIndex);
}
private void bindOfflineEscalationOptions(ViewHolder holder, OfflineEscalationData data) {
@@ -360,7 +387,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
break;
}
}
spinner.setOnItemSelectedListener(mSpinnerItemSelectListener);
spinner.setOnItemSelectedListener(mOfflineSpinnerItemSelectListener);
// Bind buttons
if (data.tollFreePhone != null) {
holder.text1View.setText(data.tollFreePhone.number);
@@ -415,11 +442,23 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
*/
private void tryStartDisclaimerAndSupport(final @SupportFeatureProvider.SupportType int type) {
if (mSupportFeatureProvider.shouldShowDisclaimerDialog(mActivity)) {
DialogFragment fragment = SupportDisclaimerDialogFragment.newInstance(mAccount, type);
DialogFragment fragment = SupportDisclaimerDialogFragment.newInstance(
mAccounts[mSelectedAccountIndex], type);
fragment.show(mActivity.getFragmentManager(), SupportDisclaimerDialogFragment.TAG);
return;
}
mSupportFeatureProvider.startSupport(mActivity, mAccount, type);
mSupportFeatureProvider.startSupport(mActivity, mAccounts[mSelectedAccountIndex], type);
}
private String[] extractAccountNames(Account[] accounts) {
String[] accountNames = new String[accounts.length+1];
for (int i = 0; i < accounts.length; i++) {
accountNames[i] = accounts[i].name;
}
accountNames[accounts.length] = mActivity.getString(
R.string.support_account_picker_add_account);
return accountNames;
}
/**
@@ -428,7 +467,7 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
private final class EscalationClickListener implements View.OnClickListener {
@Override
public void onClick(final View v) {
if (mAccount == null) {
if (mAccounts.length == 0) {
switch (v.getId()) {
case android.R.id.text1:
mMetricsFeatureProvider.action(mActivity,
@@ -490,7 +529,8 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
}
}
private final class SpinnerItemSelectListener implements AdapterView.OnItemSelectedListener {
private final class OfflineSpinnerItemSelectListener
implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
@@ -508,6 +548,26 @@ public final class SupportItemAdapter extends RecyclerView.Adapter<SupportItemAd
}
}
private final class OnlineSpinnerItemSelectListener
implements AdapterView.OnItemSelectedListener {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position == mAccounts.length) {
mActivity.startActivity(mSupportFeatureProvider.getAccountLoginIntent());
// Make sure "Add account" is not shown as selected item
parent.setSelection(mSelectedAccountIndex);
} else if (position != mSelectedAccountIndex) {
mSelectedAccountIndex = position;
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// Do nothing.
}
}
/**
* {@link RecyclerView.ViewHolder} for support items.
*/

View File

@@ -18,6 +18,7 @@ package com.android.settings.overlay;
import android.accounts.Account;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringRes;
import android.app.Activity;
import android.content.Context;
@@ -111,15 +112,16 @@ public interface SupportFeatureProvider {
void setShouldShowDisclaimerDialog(Context context, boolean shouldShow);
/**
* Returns an {@link Account} that's eligible for support options.
* Returns array of {@link Account} that's eligible for support options.
*/
Account getSupportEligibleAccount(Context context);
@NonNull
Account[] getSupportEligibleAccounts(Context context);
/**
* Starts support activity of specified type
*
* @param activity Calling activity
* @param account A account returned by {@link #getSupportEligibleAccount}
* @param account A account that selected by user
* @param type The type of support account needs.
*/
void startSupport(Activity activity, Account account, @SupportType int type);

View File

@@ -0,0 +1,139 @@
/*
* 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.app.Activity;
import android.content.Intent;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import com.android.settings.TestConfig;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
import com.android.settings.overlay.SupportFeatureProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import com.android.settings.R;
import org.robolectric.shadows.ShadowActivity;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
import static org.mockito.Mockito.when;
import static com.google.common.truth.Truth.assertThat;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SupportItemAdapterTest {
private static final String ACCOUNT_TYPE = "com.google";
private final Account USER_1 = new Account("user1", ACCOUNT_TYPE);
private final Account USER_2 = new Account("user2", ACCOUNT_TYPE);
private final Account TWO_ACCOUNTS[] = {USER_1, USER_2};
private final Account ONE_ACCOUNT[] = {USER_1};
private ShadowActivity mShadowActivity;
private Activity mActivity;
private SupportItemAdapter mSupportItemAdapter;
private SupportItemAdapter.ViewHolder mViewHolder;
@Mock
private SupportFeatureProvider mSupportFeatureProvider;
@Mock
private MetricsFeatureProvider mMetricsFeatureProvider;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mActivity = Robolectric.setupActivity(Activity.class);
mShadowActivity = shadowOf(mActivity);
final View itemView = LayoutInflater.from(mActivity).inflate(
R.layout.support_escalation_options, null);
mViewHolder = new SupportItemAdapter.ViewHolder(itemView);
// Mock this to prevent crash in testing
when(mSupportFeatureProvider.getAccountLoginIntent()).thenReturn(
new Intent(Settings.ACTION_ADD_ACCOUNT));
}
@Test
public void testBindAccountPicker_TwoAccounts_ShouldHaveTwoAccounts() {
testBindAccountPickerInner(mViewHolder, TWO_ACCOUNTS);
}
@Test
public void testBindAccountPicker_OneAccount_ShouldHaveOneAccount() {
testBindAccountPickerInner(mViewHolder, ONE_ACCOUNT);
}
@Test
public void testOnSpinnerItemClick_AddAccountClicked_AccountLoginIntentInvoked() {
bindAccountPickerInner(mViewHolder, TWO_ACCOUNTS);
final Spinner spinner = (Spinner) mViewHolder.itemView.findViewById(R.id.account_spinner);
spinner.setSelection(TWO_ACCOUNTS.length);
Robolectric.flushForegroundThreadScheduler();
verify(mSupportFeatureProvider).getAccountLoginIntent();
}
/**
* Check after {@link SupportItemAdapter#bindAccountPicker(SupportItemAdapter.ViewHolder)} is
* invoked, whether the spinner in {@paramref viewHolder} has all the data from {@paramref
* accounts}
*
* @param viewHolder holds the view that contains the spinner to test
* @param accounts holds the accounts info to be showed in spinner.
*/
private void testBindAccountPickerInner(SupportItemAdapter.ViewHolder viewHolder,
Account accounts[]) {
bindAccountPickerInner(viewHolder, accounts);
final Spinner spinner = (Spinner) viewHolder.itemView.findViewById(R.id.account_spinner);
final SpinnerAdapter adapter = spinner.getAdapter();
// Contains "Add account" option, so should be 'count+1'
assertThat(adapter.getCount()).isEqualTo(accounts.length + 1);
for (int i = 0; i < accounts.length; i++) {
assertThat(adapter.getItem(i)).isEqualTo(accounts[i].name);
}
}
/**
* Create {@link SupportItemAdapter} and bind the account picker view into
* {@paramref viewholder}
*
* @param viewHolder holds the view that contains the spinner to test
* @param accounts holds the accounts info to be showed in spinner.
*/
private void bindAccountPickerInner(SupportItemAdapter.ViewHolder viewHolder,
Account accounts[]) {
when(mSupportFeatureProvider.getSupportEligibleAccounts(mActivity)).thenReturn(accounts);
mSupportItemAdapter = new SupportItemAdapter(mActivity, null, mSupportFeatureProvider,
mMetricsFeatureProvider, null);
mSupportItemAdapter.bindAccountPicker(viewHolder);
}
}