From 64f5b796c28205f0ef1d2205b02edc11eda32ed5 Mon Sep 17 00:00:00 2001 From: tmfang Date: Tue, 12 Feb 2019 18:23:20 +0800 Subject: [PATCH 1/7] Add progress bar for Privacy screen We show up top linear progress bar when Settings is querying permission usage data. Test: visual, robotest Fixes: 123539793 Change-Id: I350068856d1708eb054d2a205d9ca857214891ce --- ...ermissionBarChartPreferenceController.java | 24 +++++-- .../privacy/PrivacyDashboardFragment.java | 27 +++++++ ...ssionBarChartPreferenceControllerTest.java | 67 ++++++++++++++++-- .../privacy/PrivacyDashboardFragmentTest.java | 70 ++++++++++++++----- .../ShadowPermissionControllerManager.java | 44 ++++++++++++ 5 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowPermissionControllerManager.java diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java index 8b9b5955fb4..876c04a9b5c 100644 --- a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java +++ b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java @@ -31,28 +31,29 @@ import android.util.Log; import android.view.View; import androidx.annotation.NonNull; -import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.Utils; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.widget.BarChartInfo; import com.android.settingslib.widget.BarChartPreference; import com.android.settingslib.widget.BarViewInfo; -import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.concurrent.Executors; public class PermissionBarChartPreferenceController extends BasePreferenceController implements - PermissionControllerManager.OnPermissionUsageResultCallback { + PermissionControllerManager.OnPermissionUsageResultCallback, LifecycleObserver, OnStart { private static final String TAG = "BarChartPreferenceCtl"; private PackageManager mPackageManager; + private PrivacyDashboardFragment mParent; private BarChartPreference mBarChartPreference; private List mOldUsageInfos; @@ -62,6 +63,10 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro mPackageManager = context.getPackageManager(); } + public void setFragment(PrivacyDashboardFragment fragment) { + mParent = fragment; + } + @Override public int getAvailabilityStatus() { return Boolean.parseBoolean( @@ -90,7 +95,13 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro } @Override - public void updateState(Preference preference) { + public void onStart() { + if (!isAvailable()) { + return; + } + + mBarChartPreference.updateLoadingState(true /* isLoading */); + mParent.setLoadingEnabled(true /* enabled */); retrievePermissionUsageData(); } @@ -104,6 +115,9 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro mBarChartPreference.setBarViewInfos(createBarViews(usageInfos)); mOldUsageInfos = usageInfos; } + + mBarChartPreference.updateLoadingState(false /* isLoading */); + mParent.setLoadingEnabled(false /* enabled */); } private void retrievePermissionUsageData() { diff --git a/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java index 5823407d2c0..be6701e05b1 100644 --- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java +++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java @@ -48,6 +48,11 @@ public class PrivacyDashboardFragment extends DashboardFragment { private static final String KEY_NOTIFICATION_WORK_PROFILE_NOTIFICATIONS = "privacy_lock_screen_work_profile_notifications"; + @VisibleForTesting + View mProgressHeader; + @VisibleForTesting + View mProgressAnimation; + @Override public int getMetricsCategory() { return SettingsEnums.TOP_LEVEL_PRIVACY; @@ -73,10 +78,17 @@ public class PrivacyDashboardFragment extends DashboardFragment { return buildPreferenceControllers(context, getSettingsLifecycle()); } + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(PermissionBarChartPreferenceController.class).setFragment(this /* fragment */); + } + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); styleActionBar(); + initLoadingBar(); } @VisibleForTesting @@ -97,6 +109,21 @@ public class PrivacyDashboardFragment extends DashboardFragment { } } + @VisibleForTesting + void initLoadingBar() { + mProgressHeader = setPinnedHeaderView(R.layout.progress_header); + mProgressAnimation = mProgressHeader.findViewById(R.id.progress_bar_animation); + setLoadingEnabled(false); + } + + @VisibleForTesting + void setLoadingEnabled(boolean enabled) { + if (mProgressHeader != null && mProgressAnimation != null) { + mProgressHeader.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); + mProgressAnimation.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); + } + } + private static List buildPreferenceControllers( Context context, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); diff --git a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java index b9476304b72..f9913f671d8 100644 --- a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java @@ -22,18 +22,25 @@ import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserManager; import android.permission.RuntimePermissionUsageInfo; import android.provider.DeviceConfig; import androidx.preference.PreferenceScreen; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowDeviceConfig; +import com.android.settings.testutils.shadow.ShadowPermissionControllerManager; +import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.widget.BarChartInfo; import com.android.settingslib.widget.BarChartPreference; import com.android.settingslib.widget.BarViewInfo; @@ -47,26 +54,41 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.androidx.fragment.FragmentController; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowDeviceConfig.class}) +@Config(shadows = {ShadowDeviceConfig.class, ShadowUserManager.class, + ShadowPermissionControllerManager.class}) public class PermissionBarChartPreferenceControllerTest { @Mock private PreferenceScreen mScreen; + @Mock + private LockPatternUtils mLockPatternUtils; private PermissionBarChartPreferenceController mController; private BarChartPreference mPreference; + private PrivacyDashboardFragment mFragment; @Before public void setUp() { MockitoAnnotations.initMocks(this); - Context Context = RuntimeEnvironment.application; - mController = new PermissionBarChartPreferenceController(Context, "test_key"); - mPreference = spy(new BarChartPreference(Context)); + final Context context = RuntimeEnvironment.application; + final UserManager userManager = context.getSystemService(UserManager.class); + final ShadowUserManager shadowUserManager = Shadow.extract(userManager); + shadowUserManager.addProfile(new UserInfo(123, null, 0)); + when(FakeFeatureFactory.setupForTest().securityFeatureProvider.getLockPatternUtils( + any(Context.class))).thenReturn(mLockPatternUtils); + + mController = spy(new PermissionBarChartPreferenceController(context, "test_key")); + mFragment = spy(FragmentController.of(new PrivacyDashboardFragment()) + .create().start().get()); + mController.setFragment(mFragment); + mPreference = spy(new BarChartPreference(context)); when(mScreen.findPreference(mController.getPreferenceKey())) .thenReturn((BarChartPreference) mPreference); } @@ -130,4 +152,41 @@ public class PermissionBarChartPreferenceControllerTest { verify(mPreference, times(1)).setBarViewInfos(any(BarViewInfo[].class)); } + + @Test + public void onStart_permissionHubEnabled_shouldShowProgressBar() { + DeviceConfig.setProperty(DeviceConfig.Privacy.NAMESPACE, + DeviceConfig.Privacy.PROPERTY_PERMISSIONS_HUB_ENABLED, "true", true); + mController.displayPreference(mScreen); + + mController.onStart(); + + verify(mFragment).setLoadingEnabled(true /* enabled */); + verify(mPreference).updateLoadingState(true /* isLoading */); + } + + @Test + public void onStart_permissionHubDisabled_shouldNotShowProgressBar() { + DeviceConfig.setProperty(DeviceConfig.Privacy.NAMESPACE, + DeviceConfig.Privacy.PROPERTY_PERMISSIONS_HUB_ENABLED, "false", false); + + mController.onStart(); + + verify(mFragment, never()).setLoadingEnabled(true /* enabled */); + verify(mPreference, never()).updateLoadingState(true /* isLoading */); + } + + @Test + public void onPermissionUsageResult_shouldHideProgressBar() { + final List infos1 = new ArrayList<>(); + final RuntimePermissionUsageInfo info1 = + new RuntimePermissionUsageInfo("permission 1", 10); + infos1.add(info1); + mController.displayPreference(mScreen); + + mController.onPermissionUsageResult(infos1); + + verify(mFragment).setLoadingEnabled(false /* enabled */); + verify(mPreference).updateLoadingState(false /* isLoading */); + } } diff --git a/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java index d073e9b80b5..161c39a772c 100644 --- a/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java @@ -16,53 +16,89 @@ package com.android.settings.privacy; -import static org.mockito.Mockito.mock; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.ActionBar; import android.content.Context; +import android.content.pm.UserInfo; import android.os.Bundle; +import android.os.UserManager; +import android.permission.PermissionControllerManager; import android.view.View; -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.RecyclerView; - -import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowPermissionControllerManager; +import com.android.settings.testutils.shadow.ShadowUserManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.Robolectric; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.androidx.fragment.FragmentController; + @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUserManager.class, ShadowPermissionControllerManager.class}) public class PrivacyDashboardFragmentTest { + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock + private PermissionControllerManager mPCM; + private Context mContext; private PrivacyDashboardFragment mFragment; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mFragment = spy(new PrivacyDashboardFragment()); + final UserManager userManager = mContext.getSystemService(UserManager.class); + final ShadowUserManager shadowUserManager = Shadow.extract(userManager); + shadowUserManager.addProfile(new UserInfo(123, null, 0)); + when(FakeFeatureFactory.setupForTest().securityFeatureProvider.getLockPatternUtils( + any(Context.class))).thenReturn(mLockPatternUtils); + mFragment = spy(FragmentController.of(new PrivacyDashboardFragment()) + .create().start().get()); } @Test public void onViewCreated_shouldCallStyleActionBar() { - final FragmentActivity activity = spy( - Robolectric.buildActivity(FragmentActivity.class).get()); - final ActionBar actionBar = mock(ActionBar.class); - - when(mFragment.getActivity()).thenReturn(activity); - when(mFragment.getSettingsLifecycle()).thenReturn(mock(Lifecycle.class)); - when(mFragment.getListView()).thenReturn(mock(RecyclerView.class)); - when(activity.getActionBar()).thenReturn(actionBar); - mFragment.onViewCreated(new View(mContext), new Bundle()); verify(mFragment).styleActionBar(); } + + @Test + public void onViewCreated_shouldInitLinearProgressBar() { + mFragment.onViewCreated(new View(mContext), new Bundle()); + + verify(mFragment).initLoadingBar(); + } + + @Test + public void updateLinearProgressbar_isVisible_shouldShowProgressBar() { + mFragment.setLoadingEnabled(true /* enabled */); + + assertThat(mFragment.mProgressHeader.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mFragment.mProgressAnimation.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateLinearProgressbar_isInVisible_shouldHideProgressBar() { + mFragment.setLoadingEnabled(false /* enabled */); + + assertThat(mFragment.mProgressHeader.getVisibility()).isEqualTo(View.INVISIBLE); + assertThat(mFragment.mProgressAnimation.getVisibility()).isEqualTo(View.INVISIBLE); + } } diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPermissionControllerManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPermissionControllerManager.java new file mode 100644 index 00000000000..091b244bdca --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowPermissionControllerManager.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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.testutils.shadow; + +import android.annotation.CallbackExecutor; +import android.content.Context; +import android.permission.PermissionControllerManager; + +import androidx.annotation.NonNull; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.util.concurrent.Executor; + +@Implements(PermissionControllerManager.class) +public class ShadowPermissionControllerManager { + + protected void __constructor__(Context context) { + // no nothing, everything is shadowed + } + + @Implementation + public void getPermissionUsages(boolean countSystem, long numMillis, + @NonNull @CallbackExecutor Executor executor, + @NonNull PermissionControllerManager.OnPermissionUsageResultCallback callback) { + + // Do nothing + } +} From 3a5d9020e8bc83dae72c8babfd84280102761616 Mon Sep 17 00:00:00 2001 From: Johnson Lu Date: Wed, 13 Feb 2019 11:50:11 +0800 Subject: [PATCH 2/7] Remove feature flags of WiFi Sharing feature Bug: 120744414 Test: Manual Change-Id: I655ebb8d149d0976063845478bc667cc4d72d96d --- .../android/settings/core/FeatureFlags.java | 1 - .../settings/wifi/AddNetworkFragment.java | 18 ++++++------- src/com/android/settings/wifi/WifiDialog.java | 26 +++++++++---------- .../android/settings/wifi/WifiSettings.java | 14 +++++----- .../WifiDetailPreferenceController.java | 3 +-- .../settings/wifi/dpp/WifiDppUtils.java | 8 ------ 6 files changed, 27 insertions(+), 43 deletions(-) diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index d1c2fd51de9..78fdfe778ad 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -26,7 +26,6 @@ public class FeatureFlags { public static final String MOBILE_NETWORK_V2 = "settings_mobile_network_v2"; public static final String WIFI_MAC_RANDOMIZATION = "settings_wifi_mac_randomization"; public static final String NETWORK_INTERNET_V2 = "settings_network_and_internet_v2"; - public static final String WIFI_SHARING = "settings_wifi_sharing"; public static final String SLICE_INJECTION = "settings_slice_injection"; public static final String MAINLINE_MODULE = "settings_mainline_module"; } diff --git a/src/com/android/settings/wifi/AddNetworkFragment.java b/src/com/android/settings/wifi/AddNetworkFragment.java index e4d0fb1ceaa..8df15a5e4d1 100644 --- a/src/com/android/settings/wifi/AddNetworkFragment.java +++ b/src/com/android/settings/wifi/AddNetworkFragment.java @@ -66,16 +66,14 @@ public class AddNetworkFragment extends InstrumentedFragment implements WifiConf mCancelBtn.setOnClickListener(this); mUIController = new WifiConfigController(this, rootView, null, getMode()); - if (WifiDppUtils.isSharingNetworkEnabled(getContext())) { - final ImageButton scannerButton = rootView.findViewById(R.id.ssid_scanner_button); - if (scannerButton != null) { - scannerButton.setVisibility(View.VISIBLE); - scannerButton.setOnClickListener((View v) -> { - // Launch QR code scanner to join a network. - getContext().startActivity( - WifiDppUtils.getEnrolleeQrCodeScannerIntent(/* ssid */ null)); - }); - } + final ImageButton scannerButton = rootView.findViewById(R.id.ssid_scanner_button); + if (scannerButton != null) { + scannerButton.setVisibility(View.VISIBLE); + scannerButton.setOnClickListener((View v) -> { + // Launch QR code scanner to join a network. + getContext().startActivity( + WifiDppUtils.getEnrolleeQrCodeScannerIntent(/* ssid */ null)); + }); } return rootView; diff --git a/src/com/android/settings/wifi/WifiDialog.java b/src/com/android/settings/wifi/WifiDialog.java index 9420c4c8ae6..4847f07b927 100644 --- a/src/com/android/settings/wifi/WifiDialog.java +++ b/src/com/android/settings/wifi/WifiDialog.java @@ -80,20 +80,18 @@ public class WifiDialog extends AlertDialog implements WifiConfigUiBase, @Override protected void onCreate(Bundle savedInstanceState) { mView = getLayoutInflater().inflate(R.layout.wifi_dialog, /* root */ null); - if (WifiDppUtils.isSharingNetworkEnabled(getContext())) { - final ImageButton scannerButton = mView.findViewById(R.id.password_scanner_button); - if (scannerButton != null) { - scannerButton.setVisibility(View.VISIBLE); - scannerButton.setOnClickListener((View v) -> { - String ssid = null; - if (mAccessPoint != null) { - ssid = mAccessPoint.getSsidStr(); - } - // Launch QR code scanner to join a network. - getContext().startActivity( - WifiDppUtils.getEnrolleeQrCodeScannerIntent(ssid)); - }); - } + final ImageButton scannerButton = mView.findViewById(R.id.password_scanner_button); + if (scannerButton != null) { + scannerButton.setVisibility(View.VISIBLE); + scannerButton.setOnClickListener((View v) -> { + String ssid = null; + if (mAccessPoint != null) { + ssid = mAccessPoint.getSsidStr(); + } + // Launch QR code scanner to join a network. + getContext().startActivity( + WifiDppUtils.getEnrolleeQrCodeScannerIntent(ssid)); + }); } setView(mView); mController = new WifiConfigController(this, mView, mAccessPoint, mMode); diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 2e12bcead49..4ca9f8680c0 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -239,14 +239,12 @@ public class WifiSettings extends RestrictedSettingsFragment mAddPreference = new ButtonPreference(prefContext); mAddPreference.setIcon(R.drawable.ic_menu_add); mAddPreference.setTitle(R.string.wifi_add_network); - if (WifiDppUtils.isSharingNetworkEnabled(getContext())) { - mAddPreference.setButtonIcon(R.drawable.ic_scan_24dp); - mAddPreference.setButtonOnClickListener((View v) -> { - // Launch QR code scanner to join a network. - getContext().startActivity( - WifiDppUtils.getEnrolleeQrCodeScannerIntent(/* ssid */ null)); - }); - } + mAddPreference.setButtonIcon(R.drawable.ic_scan_24dp); + mAddPreference.setButtonOnClickListener((View v) -> { + // Launch QR code scanner to join a network. + getContext().startActivity( + WifiDppUtils.getEnrolleeQrCodeScannerIntent(/* ssid */ null)); + }); mStatusMessagePreference = (LinkablePreference) findPreference(PREF_KEY_STATUS_MESSAGE); mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager()); diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java index 27db7cd85f9..a9c41a58633 100644 --- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java @@ -534,8 +534,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController * Returns whether the user can share the network represented by this preference with QR code. */ private boolean canShareNetwork() { - return mAccessPoint.getConfig() != null && FeatureFlagUtils.isEnabled(mContext, - FeatureFlags.WIFI_SHARING); + return mAccessPoint.getConfig() != null; } /** diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java index 7bc80e00ee0..8b333e22dfa 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java +++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java @@ -74,14 +74,6 @@ public class WifiDppUtils { * indicate test mode UI should be shown. Test UI does not make API calls. Value is a boolean.*/ public static final String EXTRA_TEST = "test"; - /** - * Returns whether the user can share the network represented by this preference with QR code. - */ - public static boolean isSharingNetworkEnabled(Context context) { - return FeatureFlagUtils.isEnabled(context, - com.android.settings.core.FeatureFlags.WIFI_SHARING); - } - /** * Returns whether the device support WiFi DPP. */ From 07ced72e67d7fb27c3c3e0a32adfe179aa6c5a65 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Fri, 15 Feb 2019 16:30:17 +0800 Subject: [PATCH 3/7] Fix [RTL] Wifi QR Scanner option overlaps wifi Password text Use android:layout_alignParentStart & android:layout_alignParentEnd to fix the RTL issue. And refine code of the scanner button. Bug: 123923904 Test: manual test Change-Id: I85491b99d22ec303e370422a81a43fc203b695fa --- res/layout/wifi_dialog.xml | 8 ++++---- .../settings/wifi/AddNetworkFragment.java | 18 ++++++++---------- src/com/android/settings/wifi/WifiDialog.java | 1 - 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/res/layout/wifi_dialog.xml b/res/layout/wifi_dialog.xml index df676fc2a21..8ee2fbaff19 100644 --- a/res/layout/wifi_dialog.xml +++ b/res/layout/wifi_dialog.xml @@ -56,6 +56,7 @@ @@ -291,6 +291,7 @@ @@ -299,12 +300,11 @@ android:id="@+id/password_scanner_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_margin="5dp" android:background="@null" android:src="@drawable/ic_scan_24dp" - android:visibility="gone" android:contentDescription="@string/wifi_add_network" /> diff --git a/src/com/android/settings/wifi/AddNetworkFragment.java b/src/com/android/settings/wifi/AddNetworkFragment.java index 8df15a5e4d1..656f8efe6a4 100644 --- a/src/com/android/settings/wifi/AddNetworkFragment.java +++ b/src/com/android/settings/wifi/AddNetworkFragment.java @@ -40,6 +40,7 @@ public class AddNetworkFragment extends InstrumentedFragment implements WifiConf final static int SUBMIT_BUTTON_ID = android.R.id.button1; @VisibleForTesting final static int CANCEL_BUTTON_ID = android.R.id.button2; + final static int SCANNER_BUTTON_ID = R.id.ssid_scanner_button; private WifiConfigController mUIController; private Button mSubmitBtn; @@ -62,20 +63,12 @@ public class AddNetworkFragment extends InstrumentedFragment implements WifiConf mSubmitBtn = rootView.findViewById(SUBMIT_BUTTON_ID); mCancelBtn = rootView.findViewById(CANCEL_BUTTON_ID); + final ImageButton scannerButton = rootView.findViewById(SCANNER_BUTTON_ID); mSubmitBtn.setOnClickListener(this); mCancelBtn.setOnClickListener(this); + scannerButton.setOnClickListener(this); mUIController = new WifiConfigController(this, rootView, null, getMode()); - final ImageButton scannerButton = rootView.findViewById(R.id.ssid_scanner_button); - if (scannerButton != null) { - scannerButton.setVisibility(View.VISIBLE); - scannerButton.setOnClickListener((View v) -> { - // Launch QR code scanner to join a network. - getContext().startActivity( - WifiDppUtils.getEnrolleeQrCodeScannerIntent(/* ssid */ null)); - }); - } - return rootView; } @@ -94,6 +87,11 @@ public class AddNetworkFragment extends InstrumentedFragment implements WifiConf case CANCEL_BUTTON_ID: handleCancelAction(); break; + case SCANNER_BUTTON_ID: + // Launch QR code scanner to join a network. + getContext().startActivity( + WifiDppUtils.getEnrolleeQrCodeScannerIntent(/* ssid */ null)); + break; } } diff --git a/src/com/android/settings/wifi/WifiDialog.java b/src/com/android/settings/wifi/WifiDialog.java index 4847f07b927..7d5f3b30f4f 100644 --- a/src/com/android/settings/wifi/WifiDialog.java +++ b/src/com/android/settings/wifi/WifiDialog.java @@ -82,7 +82,6 @@ public class WifiDialog extends AlertDialog implements WifiConfigUiBase, mView = getLayoutInflater().inflate(R.layout.wifi_dialog, /* root */ null); final ImageButton scannerButton = mView.findViewById(R.id.password_scanner_button); if (scannerButton != null) { - scannerButton.setVisibility(View.VISIBLE); scannerButton.setOnClickListener((View v) -> { String ssid = null; if (mAccessPoint != null) { From 53656a17493f7a5db4d0b66991beeb0951d51f35 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Fri, 15 Feb 2019 21:03:54 +0800 Subject: [PATCH 4/7] Hide "Pair new device" from connected device slice Fixes:124460897 Test: visual, robotests Change-Id: Id702bf77eacf29d831e00f56d0b1ab9c1f945fbb --- .../slices/BluetoothDevicesSlice.java | 28 ------------------ .../slices/BluetoothDevicesSliceTest.java | 29 ------------------- 2 files changed, 57 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java index 5673948dde8..0156ac61d0c 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java @@ -37,7 +37,6 @@ import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment; -import com.android.settings.bluetooth.BluetoothPairingDetail; import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment; import com.android.settings.core.SubSettingLauncher; import com.android.settings.slices.CustomSliceRegistry; @@ -127,11 +126,6 @@ public class BluetoothDevicesSlice implements CustomSliceable { listBuilder.addRow(rows.get(i)); } - // Add "Pair new device" if need. - if (rows.size() < DEFAULT_EXPANDED_ROW_COUNT) { - listBuilder.addRow(getPairNewDeviceRowBuilder()); - } - return listBuilder.build(); } @@ -268,26 +262,4 @@ public class BluetoothDevicesSlice implements CustomSliceable { return mContext.getResources().getQuantityString(R.plurals.show_bluetooth_devices, deviceCount, deviceCount); } - - private ListBuilder.RowBuilder getPairNewDeviceRowBuilder() { - final CharSequence title = mContext.getText(R.string.bluetooth_pairing_pref_title); - final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_menu_add); - final SliceAction sliceAction = SliceAction.createDeeplink( - getPairNewDeviceIntent(), icon, ListBuilder.ICON_IMAGE, title); - - return new ListBuilder.RowBuilder() - .setTitleItem(icon, ListBuilder.ICON_IMAGE) - .setTitle(title) - .setPrimaryAction(sliceAction); - } - - private PendingIntent getPairNewDeviceIntent() { - final Intent intent = new SubSettingLauncher(mContext) - .setDestination(BluetoothPairingDetail.class.getName()) - .setTitleRes(R.string.bluetooth_pairing_page_title) - .setSourceMetricsCategory(SettingsEnums.BLUETOOTH_PAIRING) - .toIntent(); - - return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); - } } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java index 02d7a9da679..77fc5d94eee 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSliceTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.when; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.text.TextUtils; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; @@ -122,18 +121,6 @@ public class BluetoothDevicesSliceTest { SliceTester.assertAnySliceItemContainsTitle(sliceItems, BLUETOOTH_MOCK_TITLE); } - @Test - public void getSlice_hasBluetoothDevices_shouldHavePairNewDevice() { - mockBluetoothDeviceList(1); - doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices(); - - final Slice slice = mBluetoothDevicesSlice.getSlice(); - - final List sliceItems = slice.getItems(); - SliceTester.assertAnySliceItemContainsTitle(sliceItems, - mContext.getString(R.string.bluetooth_pairing_pref_title)); - } - @Test public void getSlice_noBluetoothDevices_shouldHaveNoBluetoothDevicesTitle() { doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices(); @@ -145,17 +132,6 @@ public class BluetoothDevicesSliceTest { mContext.getString(R.string.no_bluetooth_devices)); } - @Test - public void getSlice_noBluetoothDevices_shouldNotHavePairNewDevice() { - doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices(); - - final Slice slice = mBluetoothDevicesSlice.getSlice(); - - final SliceMetadata metadata = SliceMetadata.from(mContext, slice); - assertThat(hasTitle(metadata, - mContext.getString(R.string.bluetooth_pairing_pref_title))).isFalse(); - } - @Test public void getSlice_exceedDefaultRowCount_shouldOnlyShowDefaultRows() { mockBluetoothDeviceList(BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT + 1); @@ -204,9 +180,4 @@ public class BluetoothDevicesSliceTest { mBluetoothDeviceList.add(mCachedBluetoothDevice); } } - - private boolean hasTitle(SliceMetadata metadata, String title) { - final CharSequence sliceTitle = metadata.getTitle(); - return TextUtils.equals(sliceTitle, title); - } } From 5cbd97f4ea83ce6e978404b226abf73561b5bb68 Mon Sep 17 00:00:00 2001 From: cosmohsieh Date: Tue, 29 Jan 2019 14:50:43 +0800 Subject: [PATCH 5/7] [Mac Randomization] Fix MAC randomization option does not work when ephemeral newtork WifiConfiguration is only available when saved network. So ephemeral newtork could not have wifi config to config wifi setting. In this situation, making option be not selectable. Bug: 122919564 Test: atest WifiPrivacyPreferenceControllerTest Change-Id: I8a2a1c36ec6d41a6f912da29a8b17bc2e43d9a27 --- res/values/strings.xml | 2 ++ .../wifi/details/WifiNetworkDetailsFragment.java | 1 + .../details/WifiPrivacyPreferenceController.java | 12 ++++++++++++ .../WifiPrivacyPreferenceControllerTest.java | 16 ++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 92819794fd1..110fe472ea6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2102,6 +2102,8 @@ IP settings Privacy + + Randomized MAC Add a device diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java index ec43b0ffb24..225ddbcf77c 100644 --- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java +++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java @@ -141,6 +141,7 @@ public class WifiNetworkDetailsFragment extends DashboardFragment { WifiPrivacyPreferenceController preferenceController = new WifiPrivacyPreferenceController( context); preferenceController.setWifiConfiguration(mAccessPoint.getConfig()); + preferenceController.setIsEphemeral(mAccessPoint.isEphemeral()); controllers.add(preferenceController); return controllers; diff --git a/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java b/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java index a20adc9b2ee..b79f2d49749 100644 --- a/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java @@ -25,6 +25,7 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.DropDownPreference; import androidx.preference.Preference; +import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.FeatureFlags; import com.android.settingslib.core.AbstractPreferenceController; @@ -39,6 +40,7 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im private static final String KEY_WIFI_PRIVACY = "privacy"; private WifiConfiguration mWifiConfiguration; private WifiManager mWifiManager; + private boolean mIsEphemeral = false; public WifiPrivacyPreferenceController(Context context) { super(context, KEY_WIFI_PRIVACY); @@ -50,6 +52,10 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im mWifiConfiguration = wifiConfiguration; } + public void setIsEphemeral(boolean isEphemeral) { + mIsEphemeral = isEphemeral; + } + @Override public int getAvailabilityStatus() { return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.WIFI_MAC_RANDOMIZATION) @@ -62,6 +68,12 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im final int randomizationLevel = getRandomizationValue(); dropDownPreference.setValue(Integer.toString(randomizationLevel)); updateSummary(dropDownPreference, randomizationLevel); + + // Makes preference not selectable, when this is a ephemeral network. + if (mIsEphemeral) { + preference.setSelectable(false); + dropDownPreference.setSummary(R.string.wifi_privacy_settings_ephemeral_summary); + } } @Override diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java index 1e2e718a1dd..a1af8bf272c 100644 --- a/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java @@ -93,4 +93,20 @@ public class WifiPrivacyPreferenceControllerTest { mPreferenceController.getRandomizationValue(); mPreferenceController.onPreferenceChange(mDropDownPreference, "1"); } + + @Test + public void testUpdateState_isNotEphemeralNetwork_shouldBeSelectable() { + mPreferenceController.setIsEphemeral(false); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isTrue(); + } + + @Test + public void testUpdateState_isEphemeralNetwork_shouldNotSelectable() { + mPreferenceController.setIsEphemeral(true); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isFalse(); + } } From 1e687606dd0d9b0fc06473c0a229e4bb8e2ef387 Mon Sep 17 00:00:00 2001 From: cosmohsieh Date: Wed, 13 Feb 2019 17:11:16 +0800 Subject: [PATCH 6/7] [Network Connection] Implement "show all" button for NetworkRequestDialog Add "show all" button to NetworkRequest dialog to let user be able to show all AccessPoints of onMatch() callback. Bug: 117985692 Test: atest NetworkRequestDialogFragmentTest Change-Id: I604083fd0f3ea98208d860a327733699cd4664d7 --- res/values/strings.xml | 2 + .../wifi/NetworkRequestDialogFragment.java | 43 ++++++++++++- .../NetworkRequestDialogFragmentTest.java | 62 ++++++++++++++++++- 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 7eb8efefc4b..8df95dbd9e2 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10560,6 +10560,8 @@ Something came up. The application has cancelled the request to choose a device. Connection successful + + Show all diff --git a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java index d1df7f0d6f4..ba29e3abab4 100644 --- a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java +++ b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java @@ -37,6 +37,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; +import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -69,8 +70,12 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp /** Message sent to us to stop scanning wifi and pop up timeout dialog. */ private static final int MESSAGE_STOP_SCAN_WIFI_LIST = 0; - /** Spec defines there should be 5 wifi ap on the list at most. */ + /** + * Spec defines there should be 5 wifi ap on the list at most or just show all if {@code + * mShowLimitedItem} is false. + */ private static final int MAX_NUMBER_LIST_ITEM = 5; + private boolean mShowLimitedItem = true; /** Delayed time to stop scanning wifi. */ private static final int DELAY_TIME_STOP_SCAN_MS = 30 * 1000; @@ -110,13 +115,29 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp final AlertDialog.Builder builder = new AlertDialog.Builder(context) .setCustomTitle(customTitle) .setAdapter(mDialogAdapter, this) - .setPositiveButton(R.string.cancel, (dialog, which) -> getActivity().finish()); + .setPositiveButton(R.string.cancel, (dialog, which) -> getActivity().finish()) + // Do nothings, will replace the onClickListener to avoid auto closing dialog. + .setNeutralButton(R.string.network_connection_request_dialog_showall, + null /* OnClickListener */); // Clicking list item is to connect wifi ap. final AlertDialog dialog = builder.create(); dialog.getListView() .setOnItemClickListener( (parent, view, position, id) -> this.onClick(dialog, position)); + + dialog.setOnShowListener((dialogInterface) -> { + // Replace NeutralButton onClickListener to avoid closing dialog + final Button neutralBtn = dialog.getButton(AlertDialog.BUTTON_NEUTRAL); + neutralBtn.setVisibility(View.GONE); + neutralBtn.setOnClickListener(v -> { + mShowLimitedItem = false; + renewAccessPointList(null /* List */); + notifyAdapterRefresh(); + neutralBtn.setVisibility(View.GONE); + }); + }); + return dialog; } @@ -202,6 +223,18 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp } } + private void showNeutralButton() { + final AlertDialog alertDialog = (AlertDialog) getDialog(); + if (alertDialog == null) { + return; + } + + final Button neutralBtn = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + if (neutralBtn != null) { + neutralBtn.setVisibility(View.VISIBLE); + } + } + @Override public void onResume() { super.onResume(); @@ -394,6 +427,10 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp mAccessPointKeys.add(key); } } + + if (mShowLimitedItem && (mAccessPointKeys.size() > MAX_NUMBER_LIST_ITEM)) { + showNeutralButton(); + } } /** @@ -414,7 +451,7 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp count++; // Limits how many count of items could show. - if (count >= MAX_NUMBER_LIST_ITEM) { + if (mShowLimitedItem && count >= MAX_NUMBER_LIST_ITEM) { break; } } diff --git a/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java index 0286d07d074..21b68f216f2 100644 --- a/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java @@ -33,6 +33,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback; import android.os.Bundle; +import android.view.View; import android.widget.Button; import android.widget.TextView; @@ -264,20 +265,77 @@ public class NetworkRequestDialogFragmentTest { private List createAccessPointList() { List accessPointList = spy(new ArrayList<>()); Bundle bundle = new Bundle(); + bundle.putString(KEY_SSID, "Test AP 1"); bundle.putInt(KEY_SECURITY, 1); accessPointList.add(new AccessPoint(mContext, bundle)); + bundle.putString(KEY_SSID, "Test AP 2"); bundle.putInt(KEY_SECURITY, 1); accessPointList.add(new AccessPoint(mContext, bundle)); + bundle.putString(KEY_SSID, "Test AP 3"); bundle.putInt(KEY_SECURITY, 2); - AccessPoint clickedAccessPoint = new AccessPoint(mContext, bundle); - accessPointList.add(clickedAccessPoint); + accessPointList.add(new AccessPoint(mContext, bundle)); + bundle.putString(KEY_SSID, "Test AP 4"); bundle.putInt(KEY_SECURITY, 0); accessPointList.add(new AccessPoint(mContext, bundle)); return accessPointList; } + + @Test + public void display_shouldNotShowNeutralButton() { + networkRequestDialogFragment.show(mActivity.getSupportFragmentManager(), /* tag */ null); + final AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + + final Button button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + assertThat(button).isNotNull(); + assertThat(button.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onMatchManyResult_showNeutralButton() { + networkRequestDialogFragment.show(mActivity.getSupportFragmentManager(), /* tag */ null); + final AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + + final String SSID_AP = "Test AP "; + final List scanResults = new ArrayList<>(); + for (int i = 0; i < 6 ; i ++) { + ScanResult scanResult = new ScanResult(); + scanResult.SSID = SSID_AP + i; + scanResult.capabilities = "WEP"; + scanResults.add(scanResult); + } + networkRequestDialogFragment.onMatch(scanResults); + + final Button button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + assertThat(button).isNotNull(); + assertThat(button.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void clickNeutralButton_hideNeutralButton() { + // Assert + networkRequestDialogFragment.show(mActivity.getSupportFragmentManager(), /* tag */ null); + final AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + + final String SSID_AP = "Test AP "; + final List scanResults = new ArrayList<>(); + for (int i = 0; i < 6 ; i ++) { + ScanResult scanResult = new ScanResult(); + scanResult.SSID = SSID_AP + i; + scanResult.capabilities = "WEP"; + scanResults.add(scanResult); + } + networkRequestDialogFragment.onMatch(scanResults); + + // Action + final Button button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL); + button.performClick(); + + // Check + assertThat(button.getVisibility()).isEqualTo(View.GONE); + } } From 4bd94b9053ffd9041801cd65c81812bc55ed4269 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Mon, 18 Feb 2019 16:25:23 +0800 Subject: [PATCH 7/7] Remove deprecated EXCLUSIVE field from proto Fixes: 123560880 Test: robotests Change-Id: I6ff184dc5998d1e8eadfbfa16c8c7a85ebd75d98 --- protos/contextual_card_list.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/protos/contextual_card_list.proto b/protos/contextual_card_list.proto index 37383ba4b98..69c3741cada 100644 --- a/protos/contextual_card_list.proto +++ b/protos/contextual_card_list.proto @@ -18,7 +18,6 @@ message ContextualCard { SUGGESTION = 1; POSSIBLE = 2; IMPORTANT = 3; - EXCLUSIVE = 4 [deprecated = true]; DEFERRED_SETUP = 5; }