OSDN Git Service

Add account picker to Support Tab
authorjackqdyulei <jackqdyulei@google.com>
Mon, 7 Nov 2016 22:36:02 +0000 (14:36 -0800)
committerjackqdyulei <jackqdyulei@google.com>
Wed, 30 Nov 2016 22:16:55 +0000 (14:16 -0800)
Add a spinner to select the account for user.

Bug: 32249920
Test: make RunSettingsRoboTests
Change-Id: I372d16ec5ec3230f5f2994d79f4fd27085092236

res/layout/support_account_spinner_item.xml [new file with mode: 0644]
res/layout/support_escalation_options.xml
res/values/strings.xml
src/com/android/settings/dashboard/SupportFragment.java
src/com/android/settings/dashboard/SupportItemAdapter.java
src/com/android/settings/overlay/SupportFeatureProvider.java
tests/robotests/src/com/android/settings/dashboard/SupportItemAdapterTest.java [new file with mode: 0644]

diff --git a/res/layout/support_account_spinner_item.xml b/res/layout/support_account_spinner_item.xml
new file mode 100644 (file)
index 0000000..fe37a85
--- /dev/null
@@ -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"/>
index 63cc85d..17e03ff 100644 (file)
         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>
index 9aa7608..fe214f4 100644 (file)
     <!-- 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 -->
index 63983ce..163bece 100644 (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
index 517d8d0..4e1ae78 100644 (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.
      */
index 506d1bc..24ec7b5 100644 (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);
diff --git a/tests/robotests/src/com/android/settings/dashboard/SupportItemAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/SupportItemAdapterTest.java
new file mode 100644 (file)
index 0000000..71bb4f6
--- /dev/null
@@ -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);
+    }
+
+}