From c8a65cbddf7a318f08df5266ecb3f4eb28875023 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Fri, 14 Dec 2018 16:12:18 +0800 Subject: [PATCH 01/19] Support Wi-Fi DPP metrics categories in Wi-Fi DPP activities & fragments. MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_ENROLLEE Bug: 118797380 Bug: 118794858 Bug: 118794978 Test: NA Change-Id: I62fe09029d350fa976465ac872a4f53a64a74dcd --- .../settings/wifi/dpp/WifiDppAddDeviceFragment.java | 6 ++++++ .../dpp/WifiDppChooseSavedWifiNetworkFragment.java | 6 ++++++ .../settings/wifi/dpp/WifiDppConfiguratorActivity.java | 4 +--- .../settings/wifi/dpp/WifiDppEnrolleeActivity.java | 4 +--- .../settings/wifi/dpp/WifiDppQrCodeBaseFragment.java | 8 -------- .../wifi/dpp/WifiDppQrCodeGeneratorFragment.java | 6 ++++++ .../wifi/dpp/WifiDppQrCodeScannerFragment.java | 10 ++++++++++ 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/wifi/dpp/WifiDppAddDeviceFragment.java b/src/com/android/settings/wifi/dpp/WifiDppAddDeviceFragment.java index 177e79d3b53..8d6aa6813c5 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppAddDeviceFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppAddDeviceFragment.java @@ -18,6 +18,7 @@ package com.android.settings.wifi.dpp; import android.os.Bundle; +import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; /** @@ -30,6 +31,11 @@ public class WifiDppAddDeviceFragment extends WifiDppQrCodeBaseFragment { return R.layout.wifi_dpp_add_device_fragment; } + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR; + } + @Override public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); diff --git a/src/com/android/settings/wifi/dpp/WifiDppChooseSavedWifiNetworkFragment.java b/src/com/android/settings/wifi/dpp/WifiDppChooseSavedWifiNetworkFragment.java index 7d031c18ea1..66bc349ebdf 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppChooseSavedWifiNetworkFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppChooseSavedWifiNetworkFragment.java @@ -18,6 +18,7 @@ package com.android.settings.wifi.dpp; import android.os.Bundle; +import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; /** @@ -30,6 +31,11 @@ public class WifiDppChooseSavedWifiNetworkFragment extends WifiDppQrCodeBaseFrag return R.layout.wifi_dpp_choose_saved_wifi_network_fragment; } + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR; + } + @Override public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); diff --git a/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java b/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java index e4ae292d998..6c95f09cecc 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java +++ b/src/com/android/settings/wifi/dpp/WifiDppConfiguratorActivity.java @@ -27,7 +27,6 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.android.internal.logging.nano.MetricsProto; - import com.android.settings.core.InstrumentedActivity; import com.android.settings.R; @@ -67,8 +66,7 @@ public class WifiDppConfiguratorActivity extends InstrumentedActivity implements @Override public int getMetricsCategory() { - //TODO:Should we use a new metrics category for Wi-Fi DPP? - return MetricsProto.MetricsEvent.WIFI_NETWORK_DETAILS; + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR; } @Override diff --git a/src/com/android/settings/wifi/dpp/WifiDppEnrolleeActivity.java b/src/com/android/settings/wifi/dpp/WifiDppEnrolleeActivity.java index 39d993f03f9..920e73668df 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppEnrolleeActivity.java +++ b/src/com/android/settings/wifi/dpp/WifiDppEnrolleeActivity.java @@ -27,7 +27,6 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.android.internal.logging.nano.MetricsProto; - import com.android.settings.core.InstrumentedActivity; import com.android.settings.R; @@ -47,8 +46,7 @@ public class WifiDppEnrolleeActivity extends InstrumentedActivity { @Override public int getMetricsCategory() { - //TODO:Should we use a new metrics category for Wi-Fi DPP? - return MetricsProto.MetricsEvent.WIFI_NETWORK_DETAILS; + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_ENROLLEE; } @Override diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java index e6427d9e644..b82f95349da 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java @@ -27,8 +27,6 @@ import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; -import com.android.internal.logging.nano.MetricsProto; - import com.android.settings.core.InstrumentedFragment; import com.android.settings.wifi.qrcode.QrDecorateView; import com.android.settings.R; @@ -69,12 +67,6 @@ public abstract class WifiDppQrCodeBaseFragment extends InstrumentedFragment { abstract protected int getLayout(); - @Override - public int getMetricsCategory() { - //TODO:Should we use a new metrics category for Wi-Fi DPP? - return MetricsProto.MetricsEvent.WIFI_NETWORK_DETAILS; - } - @Override public final void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java index b064253f315..ec22415d90b 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java @@ -23,6 +23,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; /** @@ -35,6 +36,11 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { return R.layout.wifi_dpp_qrcode_generator_fragment; } + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR; + } + // Container Activity must implement this interface public interface OnQrCodeGeneratorFragmentAddButtonClickedListener { public void onQrCodeGeneratorFragmentAddButtonClicked(); diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java index 342e693371b..e1723183c15 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java @@ -31,6 +31,7 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; +import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.wifi.qrcode.QrCamera; import com.android.settings.wifi.qrcode.QrDecorateView; @@ -53,6 +54,15 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl return R.layout.wifi_dpp_qrcode_scanner_fragment; } + @Override + public int getMetricsCategory() { + if (mConfiguratorMode) { + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR; + } else { + return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_ENROLLEE; + } + } + /** * Configurator container activity of the fragment should create instance with this constructor. */ From 103409bd4a11b487f14ab10c09ed04ca484018d8 Mon Sep 17 00:00:00 2001 From: pastychang Date: Thu, 13 Dec 2018 23:00:17 +0800 Subject: [PATCH 02/19] Change to use footer button of setupcompat Bug: 120805516 Test: RunSettingsRoboTests Change-Id: I0eba5f8fff37bbb13b54a4d41290ae9363905f96 --- .../face_enroll_introduction_footer.xml | 2 +- ...fingerprint_enroll_introduction_footer.xml | 2 +- res/layout/storage_wizard_footer.xml | 4 ++-- res/values/strings.xml | 2 +- src/com/android/settings/MasterClear.java | 21 ++++++++++++------- .../android/settings/MasterClearConfirm.java | 17 +++++++++------ .../com/android/settings/MasterClearTest.java | 3 ++- 7 files changed, 31 insertions(+), 20 deletions(-) diff --git a/res/layout/face_enroll_introduction_footer.xml b/res/layout/face_enroll_introduction_footer.xml index d73a8edffe3..15993f05233 100644 --- a/res/layout/face_enroll_introduction_footer.xml +++ b/res/layout/face_enroll_introduction_footer.xml @@ -39,6 +39,6 @@ style="@style/SuwGlifButton.Primary" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/suw_next_button_label" /> + android:text="@string/wizard_next" /> diff --git a/res/layout/fingerprint_enroll_introduction_footer.xml b/res/layout/fingerprint_enroll_introduction_footer.xml index 1298d829d38..47d7657f198 100644 --- a/res/layout/fingerprint_enroll_introduction_footer.xml +++ b/res/layout/fingerprint_enroll_introduction_footer.xml @@ -39,6 +39,6 @@ style="@style/SuwGlifButton.Primary" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/suw_next_button_label" /> + android:text="@string/wizard_next" /> diff --git a/res/layout/storage_wizard_footer.xml b/res/layout/storage_wizard_footer.xml index 0ff40aa9c21..a4cda18a7be 100644 --- a/res/layout/storage_wizard_footer.xml +++ b/res/layout/storage_wizard_footer.xml @@ -27,7 +27,7 @@ android:layout_height="wrap_content" android:onClick="onNavigateBack" android:visibility="gone" - android:text="@string/suw_back_button_label" /> + android:text="@string/wizard_back" /> + android:text="@string/wizard_next" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 6aacb0a8da6..fa69fc6fed1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6733,7 +6733,7 @@ Back Next - + Finish diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java index 8e745a3823f..61a247dd38a 100644 --- a/src/com/android/settings/MasterClear.java +++ b/src/com/android/settings/MasterClear.java @@ -66,7 +66,9 @@ import com.android.settings.password.ConfirmLockPattern; import com.android.settingslib.RestrictedLockUtilsInternal; import com.google.android.setupcompat.TemplateLayout; -import com.google.android.setupdesign.template.ButtonFooterMixin; +import com.google.android.setupcompat.item.FooterButton; +import com.google.android.setupcompat.item.FooterButton.ButtonType; +import com.google.android.setupcompat.template.ButtonFooterMixin; import java.util.List; @@ -96,7 +98,7 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL private View mContentView; @VisibleForTesting - Button mInitiateButton; + FooterButton mInitiateButton; private View mExternalStorageContainer; @VisibleForTesting CheckBox mExternalStorage; @@ -416,12 +418,15 @@ public class MasterClear extends InstrumentedFragment implements OnGlobalLayoutL final TemplateLayout layout = mContentView.findViewById(R.id.setup_wizard_layout); final ButtonFooterMixin buttonFooterMixin = layout.getMixin(ButtonFooterMixin.class); - buttonFooterMixin.removeAllViews(); - buttonFooterMixin.addSpace(); - buttonFooterMixin.addSpace(); - mInitiateButton = buttonFooterMixin.addButton(R.string.master_clear_button_text, - R.style.SuwGlifButton_Primary); - mInitiateButton.setOnClickListener(mInitiateListener); + buttonFooterMixin.setPrimaryButton( + new FooterButton( + getActivity(), + R.string.master_clear_button_text, + mInitiateListener, + ButtonType.OTHER, + R.style.SuwGlifButton_Primary) + ); + mInitiateButton = buttonFooterMixin.getPrimaryButton(); } private void getContentDescription(View v, StringBuffer description) { diff --git a/src/com/android/settings/MasterClearConfirm.java b/src/com/android/settings/MasterClearConfirm.java index ffbb2aa1307..b2bf838cfbf 100644 --- a/src/com/android/settings/MasterClearConfirm.java +++ b/src/com/android/settings/MasterClearConfirm.java @@ -45,7 +45,9 @@ import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper; import com.android.settingslib.RestrictedLockUtilsInternal; import com.google.android.setupcompat.TemplateLayout; -import com.google.android.setupdesign.template.ButtonFooterMixin; +import com.google.android.setupcompat.item.FooterButton; +import com.google.android.setupcompat.item.FooterButton.ButtonType; +import com.google.android.setupcompat.template.ButtonFooterMixin; /** * Confirm and execute a reset of the device to a clean "just out of the box" @@ -153,11 +155,14 @@ public class MasterClearConfirm extends InstrumentedFragment { final TemplateLayout layout = mContentView.findViewById(R.id.setup_wizard_layout); final ButtonFooterMixin buttonFooterMixin = layout.getMixin(ButtonFooterMixin.class); - buttonFooterMixin.removeAllViews(); - buttonFooterMixin.addSpace(); - buttonFooterMixin.addSpace(); - buttonFooterMixin.addButton(R.string.master_clear_button_text, - R.style.SuwGlifButton_Primary).setOnClickListener(mFinalClickListener); + buttonFooterMixin.setPrimaryButton( + new FooterButton( + getActivity(), + R.string.master_clear_button_text, + mFinalClickListener, + ButtonType.OTHER, + R.style.SuwGlifButton_Primary) + ); } private void setUpActionBarAndTitle() { diff --git a/tests/robotests/src/com/android/settings/MasterClearTest.java b/tests/robotests/src/com/android/settings/MasterClearTest.java index 21cb09cc86a..388440ef968 100644 --- a/tests/robotests/src/com/android/settings/MasterClearTest.java +++ b/tests/robotests/src/com/android/settings/MasterClearTest.java @@ -51,6 +51,7 @@ import android.widget.ScrollView; import androidx.fragment.app.FragmentActivity; import com.android.settings.testutils.shadow.ShadowUtils; +import com.google.android.setupcompat.item.FooterButton; import org.junit.Before; import org.junit.Test; @@ -386,7 +387,7 @@ public class MasterClearTest { public void testOnGlobalLayout_shouldNotRemoveListener() { final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); mMasterClear.mScrollView = mScrollView; - mMasterClear.mInitiateButton = mock(Button.class); + mMasterClear.mInitiateButton = mock(FooterButton.class); doReturn(true).when(mMasterClear).hasReachedBottom(any()); when(mScrollView.getViewTreeObserver()).thenReturn(viewTreeObserver); From 74cb72433a5b13601f437f981c769f2766914545 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Fri, 14 Dec 2018 12:45:04 -0800 Subject: [PATCH 03/19] Add wifi connection info to the multi-network header The Network & internet page will have a dynamic header at the top when users have more than one mobile subscription, showing information about connectivity. This CL adds a preference to this header when there is a wifi connection, showing the same information as on the wifi-page (connection strength, speed rating if available, etc.). Bug: 116349402 Test: make RunSettingsRoboTests Change-Id: Ia80d6e236a4996b501372ac4cd8e46ba6c5f8841 --- .../network/MultiNetworkHeaderController.java | 18 +- .../WifiConnectionPreferenceController.java | 177 ++++++++++++++++++ .../MultiNetworkHeaderControllerTest.java | 25 ++- ...ifiConnectionPreferenceControllerTest.java | 161 ++++++++++++++++ 4 files changed, 374 insertions(+), 7 deletions(-) create mode 100644 src/com/android/settings/wifi/WifiConnectionPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java diff --git a/src/com/android/settings/network/MultiNetworkHeaderController.java b/src/com/android/settings/network/MultiNetworkHeaderController.java index 881aaa2ba94..8860c477e9c 100644 --- a/src/com/android/settings/network/MultiNetworkHeaderController.java +++ b/src/com/android/settings/network/MultiNetworkHeaderController.java @@ -18,7 +18,9 @@ package com.android.settings.network; import android.content.Context; +import com.android.internal.logging.nano.MetricsProto; import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.WifiConnectionPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import androidx.annotation.VisibleForTesting; @@ -29,9 +31,11 @@ import androidx.preference.PreferenceScreen; // are two or more active mobile subscriptions. It shows an overview of available network // connections with an entry for wifi (if connected) and an entry for each subscription. public class MultiNetworkHeaderController extends BasePreferenceController implements + WifiConnectionPreferenceController.UpdateListener, SubscriptionsPreferenceController.UpdateListener { public static final String TAG = "MultiNetworkHdrCtrl"; + private WifiConnectionPreferenceController mWifiController; private SubscriptionsPreferenceController mSubscriptionsController; private PreferenceCategory mPreferenceCategory; @@ -40,13 +44,22 @@ public class MultiNetworkHeaderController extends BasePreferenceController imple } public void init(Lifecycle lifecycle) { + mWifiController = createWifiController(lifecycle); mSubscriptionsController = createSubscriptionsController(lifecycle); - // TODO(asargent) - add in a controller for showing wifi status here + } + + @VisibleForTesting + WifiConnectionPreferenceController createWifiController(Lifecycle lifecycle) { + final int prefOrder = 0; + return new WifiConnectionPreferenceController(mContext, lifecycle, this, mPreferenceKey, + prefOrder, MetricsProto.MetricsEvent.SETTINGS_NETWORK_CATEGORY); } @VisibleForTesting SubscriptionsPreferenceController createSubscriptionsController(Lifecycle lifecycle) { - return new SubscriptionsPreferenceController(mContext, lifecycle, this, mPreferenceKey, 10); + final int prefStartOrder = 10; + return new SubscriptionsPreferenceController(mContext, lifecycle, this, mPreferenceKey, + prefStartOrder); } @Override @@ -54,6 +67,7 @@ public class MultiNetworkHeaderController extends BasePreferenceController imple super.displayPreference(screen); mPreferenceCategory = (PreferenceCategory) screen.findPreference(mPreferenceKey); mPreferenceCategory.setVisible(isAvailable()); + mWifiController.displayPreference(screen); mSubscriptionsController.displayPreference(screen); } diff --git a/src/com/android/settings/wifi/WifiConnectionPreferenceController.java b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java new file mode 100644 index 00000000000..b73bce981c2 --- /dev/null +++ b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2018 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.wifi; + +import android.content.Context; +import android.os.Bundle; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.AccessPointPreference; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; + +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +/** + * This places a preference into a PreferenceGroup owned by some parent + * controller class when there is a wifi connection present. + */ +public class WifiConnectionPreferenceController extends AbstractPreferenceController implements + WifiTracker.WifiListener { + + private static final String TAG = "WifiConnPrefCtrl"; + + private static final String KEY = "active_wifi_connection"; + + private UpdateListener mUpdateListener; + private Context mPrefContext; + private String mPreferenceGroupKey; + private PreferenceGroup mPreferenceGroup; + private WifiTracker mWifiTracker; + private AccessPointPreference mPreference; + private AccessPointPreference.UserBadgeCache mBadgeCache; + private int order; + private int mMetricsCategory; + + /** + * Used to notify a parent controller that this controller has changed in availability, or has + * updated the content in the preference that it manages. + */ + public interface UpdateListener { + void onChildrenUpdated(); + } + + /** + * @param context the context for the UI where we're placing the preference + * @param lifecycle for listening to lifecycle events for the UI + * @param updateListener for notifying a parent controller of changes + * @param preferenceGroupKey the key to use to lookup the PreferenceGroup where this controller + * will add its preference + * @param order the order that the preference added by this controller should use - + * useful when this preference needs to be ordered in a specific way + * relative to others in the PreferenceGroup + * @param metricsCategory - the category to use as the source when handling the click on the + * pref to go to the wifi connection detail page + */ + public WifiConnectionPreferenceController(Context context, Lifecycle lifecycle, + UpdateListener updateListener, String preferenceGroupKey, int order, + int metricsCategory) { + super(context); + mUpdateListener = updateListener; + mPreferenceGroupKey = preferenceGroupKey; + mWifiTracker = WifiTrackerFactory.create(context, this, lifecycle, true /* includeSaved */, + true /* includeScans */); + this.order = order; + mMetricsCategory = metricsCategory; + mBadgeCache = new AccessPointPreference.UserBadgeCache(context.getPackageManager()); + } + + @Override + public boolean isAvailable() { + return mWifiTracker.isConnected() && getCurrentAccessPoint() != null; + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreferenceGroup = (PreferenceGroup) screen.findPreference(mPreferenceGroupKey); + mPrefContext = screen.getContext(); + update(); + } + + private AccessPoint getCurrentAccessPoint() { + for (AccessPoint accessPoint : mWifiTracker.getAccessPoints()) { + if (accessPoint.isActive()) { + return accessPoint; + } + } + return null; + } + + private void updatePreference(AccessPoint accessPoint) { + if (mPreference != null) { + mPreferenceGroup.removePreference(mPreference); + mPreference = null; + } + if (accessPoint == null) { + return; + } + if (mPrefContext != null) { + mPreference = new AccessPointPreference(accessPoint, mPrefContext, mBadgeCache, + R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */); + mPreference.setKey(KEY); + mPreference.refresh(); + mPreference.setOrder(order); + + mPreference.setOnPreferenceClickListener(pref -> { + Bundle args = new Bundle(); + mPreference.getAccessPoint().saveWifiState(args); + new SubSettingLauncher(mPrefContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(args) + .setSourceMetricsCategory(mMetricsCategory) + .launch(); + return true; + }); + mPreferenceGroup.addPreference(mPreference); + } + } + + private void update() { + AccessPoint connectedAccessPoint = null; + if (mWifiTracker.isConnected()) { + connectedAccessPoint = getCurrentAccessPoint(); + } + if (connectedAccessPoint == null) { + updatePreference(null); + } else { + if (mPreference == null || !mPreference.getAccessPoint().equals(connectedAccessPoint)) { + updatePreference(connectedAccessPoint); + } else if (mPreference != null) { + mPreference.refresh(); + } + } + mUpdateListener.onChildrenUpdated(); + } + + @Override + public void onWifiStateChanged(int state) { + update(); + } + + @Override + public void onConnectedChanged() { + update(); + } + + @Override + public void onAccessPointsChanged() { + update(); + } +} diff --git a/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java b/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java index fbd78672b3d..b4ebcc4be18 100644 --- a/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.telephony.SubscriptionManager; +import com.android.settings.wifi.WifiConnectionPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -55,6 +56,8 @@ public class MultiNetworkHeaderControllerTest { @Mock private PreferenceCategory mPreferenceCategory; @Mock + private WifiConnectionPreferenceController mWifiController; + @Mock private SubscriptionsPreferenceController mSubscriptionsController; @Mock private SubscriptionManager mSubscriptionManager; @@ -74,6 +77,7 @@ public class MultiNetworkHeaderControllerTest { when(mPreferenceScreen.findPreference(eq(KEY_HEADER))).thenReturn(mPreferenceCategory); mHeaderController = spy(new MultiNetworkHeaderController(mContext, KEY_HEADER)); + doReturn(mWifiController).when(mHeaderController).createWifiController(mLifecycle); doReturn(mSubscriptionsController).when(mHeaderController).createSubscriptionsController( mLifecycle); } @@ -85,8 +89,9 @@ public class MultiNetworkHeaderControllerTest { // When calling displayPreference, the header itself should only be visible if the // subscriptions controller says it is available. This is a helper for test cases of this logic. - private void displayPreferenceTest(boolean subscriptionsAvailable, + private void displayPreferenceTest(boolean wifiAvailable, boolean subscriptionsAvailable, boolean setVisibleExpectedValue) { + when(mWifiController.isAvailable()).thenReturn(wifiAvailable); when(mSubscriptionsController.isAvailable()).thenReturn(subscriptionsAvailable); mHeaderController.init(mLifecycle); @@ -96,13 +101,23 @@ public class MultiNetworkHeaderControllerTest { } @Test - public void displayPreference_subscriptionsNotAvailable_categoryIsNotVisible() { - displayPreferenceTest(false, false); + public void displayPreference_bothNotAvailable_categoryIsNotVisible() { + displayPreferenceTest(false, false, false); } @Test - public void displayPreference_subscriptionsAvailable_categoryIsVisible() { - displayPreferenceTest(true, true); + public void displayPreference_wifiAvailableButNotSubscriptions_categoryIsNotVisible() { + displayPreferenceTest(true, false, false); + } + + @Test + public void displayPreference_subscriptionsAvailableButNotWifi_categoryIsVisible() { + displayPreferenceTest(false, true, true); + } + + @Test + public void displayPreference_bothAvailable_categoryIsVisible() { + displayPreferenceTest(true, true, true); } @Test diff --git a/tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java new file mode 100644 index 00000000000..703731858c3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 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.network; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +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 com.android.settings.wifi.WifiConnectionPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.AccessPointPreference; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.Arrays; + +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class WifiConnectionPreferenceControllerTest { + private static final String KEY = "wifi_connection"; + + @Mock + WifiTracker mWifiTracker; + @Mock + PreferenceScreen mScreen; + @Mock + PreferenceCategory mPreferenceCategory; + + private Context mContext; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; + private WifiConnectionPreferenceController mController; + private int mOnChildUpdatedCount; + private WifiConnectionPreferenceController.UpdateListener mUpdateListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + WifiTrackerFactory.setTestingWifiTracker(mWifiTracker); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + when(mScreen.findPreference(eq(KEY))).thenReturn(mPreferenceCategory); + when(mScreen.getContext()).thenReturn(mContext); + mUpdateListener = () -> mOnChildUpdatedCount++; + + mController = new WifiConnectionPreferenceController(mContext, mLifecycle, mUpdateListener, + KEY, 0, 0); + } + + @Test + public void isAvailable_noWiFiConnection_availableIsFalse() { + when(mWifiTracker.isConnected()).thenReturn(false); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void displayPreference_noWiFiConnection_noPreferenceAdded() { + when(mWifiTracker.isConnected()).thenReturn(false); + when(mWifiTracker.getAccessPoints()).thenReturn(new ArrayList<>()); + mController.displayPreference(mScreen); + verify(mPreferenceCategory, never()).addPreference(any()); + } + + @Test + public void displayPreference_hasWiFiConnection_preferenceAdded() { + when(mWifiTracker.isConnected()).thenReturn(true); + final AccessPoint accessPoint = mock(AccessPoint.class); + when(accessPoint.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint)); + mController.displayPreference(mScreen); + verify(mPreferenceCategory).addPreference(any(AccessPointPreference.class)); + } + + @Test + public void onConnectedChanged_wifiBecameDisconnected_preferenceRemoved() { + when(mWifiTracker.isConnected()).thenReturn(true); + final AccessPoint accessPoint = mock(AccessPoint.class); + + when(accessPoint.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint)); + mController.displayPreference(mScreen); + final ArgumentCaptor captor = ArgumentCaptor.forClass( + AccessPointPreference.class); + verify(mPreferenceCategory).addPreference(captor.capture()); + final AccessPointPreference pref = captor.getValue(); + + when(mWifiTracker.isConnected()).thenReturn(false); + when(mWifiTracker.getAccessPoints()).thenReturn(new ArrayList<>()); + final int onUpdatedCountBefore = mOnChildUpdatedCount; + mController.onConnectedChanged(); + verify(mPreferenceCategory).removePreference(pref); + assertThat(mOnChildUpdatedCount).isEqualTo(onUpdatedCountBefore + 1); + } + + + @Test + public void onAccessPointsChanged_wifiBecameConnectedToDifferentAP_preferenceReplaced() { + when(mWifiTracker.isConnected()).thenReturn(true); + final AccessPoint accessPoint1 = mock(AccessPoint.class); + + when(accessPoint1.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint1)); + mController.displayPreference(mScreen); + final ArgumentCaptor captor = ArgumentCaptor.forClass( + AccessPointPreference.class); + + + final AccessPoint accessPoint2 = mock(AccessPoint.class); + when(accessPoint1.isActive()).thenReturn(false); + when(accessPoint2.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint1, accessPoint2)); + final int onUpdatedCountBefore = mOnChildUpdatedCount; + mController.onAccessPointsChanged(); + + verify(mPreferenceCategory, times(2)).addPreference(captor.capture()); + final AccessPointPreference pref1 = captor.getAllValues().get(0); + final AccessPointPreference pref2 = captor.getAllValues().get(1); + assertThat(pref1.getAccessPoint()).isEqualTo(accessPoint1); + assertThat(pref2.getAccessPoint()).isEqualTo(accessPoint2); + verify(mPreferenceCategory).removePreference(eq(pref1)); + assertThat(mOnChildUpdatedCount).isEqualTo(onUpdatedCountBefore + 1); + } +} From 3aac42568cc6a765c186ad082743e10fe06f1931 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 17 Dec 2018 13:26:18 -0800 Subject: [PATCH 04/19] Add feature flag for settings slice injection Bug: 120803703 Test: Manual Change-Id: If5ff1fc867d010953a5aa48b2ba61a4c467b6579 --- src/com/android/settings/core/FeatureFlags.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index b35077894f2..7f14c0d8e2c 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -27,4 +27,5 @@ public class FeatureFlags { 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"; } From fcf433b788287ca1b0ed34caf7e99bd54089ba02 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 17 Dec 2018 15:18:03 -0800 Subject: [PATCH 05/19] Add more owners in Settings Change-Id: I5a48cb2955cca002d1194cfdbbcb00b2fb7530b2 Fixes: 120916672 Test: rebuild --- OWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OWNERS b/OWNERS index d2bc1ff4857..37124b23fc9 100644 --- a/OWNERS +++ b/OWNERS @@ -7,8 +7,11 @@ asargent@google.com dehboxturtle@google.com dhnishi@google.com dling@google.com +edgarwang@google.com jackqdyulei@google.com mfritze@google.com +rafftsai@google.com +tmfang@google.com zhfan@google.com # Emergency approvers in case the above are not available From 107743cbc58b9a36e95f465a953a61311c08cf52 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 17 Dec 2018 15:57:33 -0800 Subject: [PATCH 06/19] Move suggestion from LOCK_SCREEN to DEFAULT. These 2 categories behaves the same, moving suggestions to DEFAULT can save a PackageManager query in SettingsIntelligence. Bug: 121158263 Test: manual Change-Id: I24c84aa61b272120a77bda0ec27b38690f2b4d4d --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index caca44439cc..90f4ca4d7ac 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1626,7 +1626,7 @@ android:icon="@drawable/ic_settings_security"> - + From fb0a0818487de7300bd107b57ca93cd09cb93434 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Thu, 13 Dec 2018 11:45:08 +0800 Subject: [PATCH 07/19] Fine tune UI layout files of WifiDppQrCodeScannerFragment & WifiDppQrCodeGeneratorFragment 1. Remove footer in landscape QR code scanner fragment 2. Move header icon from activity layout file to header layout file 3. Fine tune header layout file 4. set icons for the 2 fragments Bug: 118797380 Bug: 118794858 Test: atest WifiDppConfiguratorActivityTest atest WifiDppQrCodeScannerFragmentTest atest WifiDppQrCodeGeneratorFragmentTest atest RunSettingsRoboTests Change-Id: I7e547d7c29dd947a890e902e61b8244fec399354 --- .../wifi_dpp_qrcode_scanner_fragment.xml | 3 -- res/layout/wifi_dpp_activity.xml | 6 --- res/layout/wifi_dpp_fragment_header.xml | 40 +++++++++++++++---- .../wifi/dpp/WifiDppQrCodeBaseFragment.java | 6 +++ .../dpp/WifiDppQrCodeGeneratorFragment.java | 3 +- .../dpp/WifiDppQrCodeScannerFragment.java | 2 + 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml b/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml index 0c938f82ede..e2df31c183b 100644 --- a/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml +++ b/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml @@ -44,8 +44,5 @@ android:layout_height="wrap_content" android:layout_gravity="center"/> - - diff --git a/res/layout/wifi_dpp_activity.xml b/res/layout/wifi_dpp_activity.xml index a833dcc757c..cb82f661c13 100644 --- a/res/layout/wifi_dpp_activity.xml +++ b/res/layout/wifi_dpp_activity.xml @@ -22,12 +22,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - - + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp"> - + - + android:gravity="center_horizontal" + android:textAlignment="center" + android:layout_marginTop="8dp" + android:paddingStart="32dp" + android:paddingEnd="32dp"/> + + diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java index cddd55cdd40..5257178644f 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java @@ -47,6 +47,7 @@ import com.android.settings.R; * {@code WifiDppAddDeviceFragment} */ public abstract class WifiDppQrCodeBaseFragment extends InstrumentedFragment { + private ImageView mHeaderIcon; private TextView mTitle; private TextView mDescription; @@ -89,6 +90,7 @@ public abstract class WifiDppQrCodeBaseFragment extends InstrumentedFragment { } private void initView(View view) { + mHeaderIcon = view.findViewById(R.id.header_icon); mTitle = view.findViewById(R.id.title); mDescription = view.findViewById(R.id.description); @@ -108,6 +110,10 @@ public abstract class WifiDppQrCodeBaseFragment extends InstrumentedFragment { mButtonRight = view.findViewById(R.id.button_right); } + protected void setHeaderIconImageResource(int resId) { + mHeaderIcon.setImageResource(resId); + } + protected void setTitle(String title) { mTitle.setText(title); } diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java index b064253f315..6358e066d8d 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java @@ -45,6 +45,7 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + setHeaderIconImageResource(R.drawable.ic_qrcode_24dp); WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity()) .getWifiNetworkConfig(); if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) { @@ -79,7 +80,7 @@ public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.next_label); - item.setIcon(R.drawable.ic_menu_add); + item.setIcon(R.drawable.ic_scan_24dp); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); super.onCreateOptionsMenu(menu, inflater); diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java index 5689c56ddad..a555d5f39d3 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java @@ -76,6 +76,8 @@ public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment impl public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + setHeaderIconImageResource(R.drawable.ic_scan_24dp); + if (mConfiguratorMode) { setTitle(getString(R.string.wifi_dpp_add_device_to_network)); From 65c8d1df691824347c952461b78b1f6227511aff Mon Sep 17 00:00:00 2001 From: cosmohsieh Date: Sat, 15 Dec 2018 01:20:27 +0800 Subject: [PATCH 08/19] Add internal WifiTracker to get correct AccessPoint for WifiNetworkRequestDialog Use WifiTracker to get correct and constantly updated Wifi status of AccessPoint. Bug: 117399926 Test: RunSettingsRoboTests Change-Id: I5e4316f6acb7787dcaab150a293068852beb76e0 --- .../wifi/NetworkRequestDialogFragment.java | 173 ++++++++++++++---- .../NetworkRequestDialogActivityTest.java | 8 + .../NetworkRequestDialogFragmentTest.java | 133 ++++++++------ 3 files changed, 213 insertions(+), 101 deletions(-) diff --git a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java index c627a2e6628..30c2cd96f16 100644 --- a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java +++ b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java @@ -30,6 +30,7 @@ import android.os.Message; import android.widget.BaseAdapter; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.preference.internal.PreferenceImageView; @@ -46,7 +47,10 @@ import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.wifi.NetworkRequestErrorDialogFragment.ERROR_DIALOG_TYPE; import com.android.settingslib.Utils; +import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; import java.util.ArrayList; import java.util.List; @@ -62,10 +66,14 @@ 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. */ + private static final int MAX_NUMBER_LIST_ITEM = 5; + /** Delayed time to stop scanning wifi. */ private static final int DELAY_TIME_STOP_SCAN_MS = 30 * 1000; private List mAccessPointList; + private FilterWifiTracker mFilterWifiTracker; private AccessPointAdapter mDialogAdapter; private NetworkRequestUserSelectionCallback mUserSelectionCallback; @@ -159,6 +167,19 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp if (wifiManager != null) { wifiManager.unregisterNetworkRequestMatchCallback(this); } + + if (mFilterWifiTracker != null) { + mFilterWifiTracker.onPause(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mFilterWifiTracker != null) { + mFilterWifiTracker.onDestroy(); + mFilterWifiTracker = null; + } } @Override @@ -172,6 +193,11 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp } // Sets time-out to stop scanning. mHandler.sendEmptyMessageDelayed(MESSAGE_STOP_SCAN_WIFI_LIST, DELAY_TIME_STOP_SCAN_MS); + + if (mFilterWifiTracker == null) { + mFilterWifiTracker = new FilterWifiTracker(getActivity(), getSettingsLifecycle()); + } + mFilterWifiTracker.onResume(); } private final Handler mHandler = new Handler() { @@ -268,17 +294,33 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp @Override public void onMatch(List scanResults) { - // TODO(b/119846365): Checks if we could escalate the converting effort. - // Converts ScanResult to WifiConfiguration. - List wifiConfigurations = null; - final WifiManager wifiManager = getContext().getApplicationContext() - .getSystemService(WifiManager.class); - if (wifiManager != null) { - wifiConfigurations = wifiManager.getAllMatchingWifiConfigs(scanResults); + mHandler.removeMessages(MESSAGE_STOP_SCAN_WIFI_LIST); + renewAccessPointList(scanResults); + + notifyAdapterRefresh(); + } + + // Updates internal AccessPoint list from WifiTracker. scanResults are used to update key list + // of AccessPoint, and could be null if there is no necessary to update key list. + private void renewAccessPointList(List scanResults) { + if (mFilterWifiTracker == null) { + return; } - setUpAccessPointList(wifiConfigurations); + // TODO(b/119846365): Checks if we could escalate the converting effort. + // Updates keys of scanResults into FilterWifiTracker for updating matched AccessPoints. + if (scanResults != null) { + mFilterWifiTracker.updateKeys(scanResults); + } + // Re-gets matched AccessPoints from WifiTracker. + final List list = getAccessPointList(); + list.clear(); + list.addAll(mFilterWifiTracker.getAccessPoints()); + } + + @VisibleForTesting + void notifyAdapterRefresh() { if (getDialogAdapter() != null) { getDialogAdapter().notifyDataSetChanged(); } @@ -286,48 +328,99 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp @Override public void onUserSelectionConnectSuccess(WifiConfiguration wificonfiguration) { - if (getDialogAdapter() != null) { - updateAccessPointListItem(wificonfiguration); - getDialogAdapter().notifyDataSetChanged(); - } + // Dismisses current dialog, since connection is success. + dismiss(); } @Override public void onUserSelectionConnectFailure(WifiConfiguration wificonfiguration) { - if (mDialogAdapter != null) { - updateAccessPointListItem(wificonfiguration); - getDialogAdapter().notifyDataSetChanged(); - } + stopScanningAndPopErrorDialog(ERROR_DIALOG_TYPE.ABORT); } - private void updateAccessPointListItem(WifiConfiguration wificonfiguration) { - if (wificonfiguration == null) { - return; + private final class FilterWifiTracker { + private final List mAccessPointKeys; + private final WifiTracker mWifiTracker; + + public FilterWifiTracker(Context context, Lifecycle lifecycle) { + mWifiTracker = WifiTrackerFactory.create(context, mWifiListener, + lifecycle, /* includeSaved */ true, /* includeScans */ true); + mAccessPointKeys = new ArrayList<>(); } - final List accessPointList = getAccessPointList(); - final int accessPointListSize = accessPointList.size(); + /** + * Updates key list from input. {@code onMatch()} may be called in multi-times according + * wifi scanning result, so needs patchwork here. + */ + public void updateKeys(List scanResults) { + for (ScanResult scanResult : scanResults) { + final String key = AccessPoint.getKey(scanResult); + if (!mAccessPointKeys.contains(key)) { + mAccessPointKeys.add(key); + } + } + } - for (int i = 0; i < accessPointListSize; i++) { - AccessPoint accessPoint = accessPointList.get(i); - // It is the same AccessPoint SSID, and should be replaced to update latest properties. - if (accessPoint.matches(wificonfiguration)) { - accessPointList.set(i, new AccessPoint(getContext(), wificonfiguration)); - break; + /** + * Returns only AccessPoints whose key is in {@code mAccessPointKeys}. + * + * @return List of matched AccessPoints. + */ + public List getAccessPoints() { + final List allAccessPoints = mWifiTracker.getAccessPoints(); + final List result = new ArrayList<>(); + + // The order should be kept, because order means wifi score (sorting in WifiTracker). + int count = 0; + for (AccessPoint accessPoint : allAccessPoints) { + final String key = accessPoint.getKey(); + if (mAccessPointKeys.contains(key)) { + result.add(accessPoint); + + count++; + // Limits how many count of items could show. + if (count >= MAX_NUMBER_LIST_ITEM) { + break; + } + } + } + + return result; + } + + private WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() { + + @Override + public void onWifiStateChanged(int state) { + notifyAdapterRefresh(); + } + + @Override + public void onConnectedChanged() { + notifyAdapterRefresh(); + } + + @Override + public void onAccessPointsChanged() { + notifyAdapterRefresh(); + } + }; + + public void onDestroy() { + if (mWifiTracker != null) { + mWifiTracker.onDestroy(); + } + } + + public void onResume() { + if (mWifiTracker != null) { + mWifiTracker.onStart(); + } + } + + public void onPause() { + if (mWifiTracker != null) { + mWifiTracker.onStop(); } } } - - private void setUpAccessPointList(List wifiConfigurations) { - // Grants for zero size input, since maybe current wifi is off or somethings are wrong. - if (wifiConfigurations == null) { - return; - } - - final List accessPointList = getAccessPointList(); - accessPointList.clear(); - for (WifiConfiguration config : wifiConfigurations) { - accessPointList.add(new AccessPoint(getContext(), config)); - } - } } diff --git a/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogActivityTest.java b/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogActivityTest.java index 48f8ec0099d..107da79d8c5 100644 --- a/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogActivityTest.java +++ b/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogActivityTest.java @@ -18,9 +18,13 @@ package com.android.settings.wifi; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + import androidx.appcompat.app.AlertDialog; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,6 +38,10 @@ public class NetworkRequestDialogActivityTest { @Test public void LaunchActivity_shouldShowNetworkRequestDialog() { + // Mocks fake WifiTracker, in case of exception in NetworkRequestDialogFragment.onResume(). + WifiTracker wifiTracker = mock(WifiTracker.class); + WifiTrackerFactory.setTestingWifiTracker(wifiTracker); + Robolectric.setupActivity(NetworkRequestDialogActivity.class); AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); diff --git a/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java index 343d170e7ed..e64fae714e6 100644 --- a/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/wifi/NetworkRequestDialogFragmentTest.java @@ -51,6 +51,10 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; + +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; + import org.robolectric.shadows.ShadowLooper; @RunWith(RobolectricTestRunner.class) @@ -58,16 +62,21 @@ import org.robolectric.shadows.ShadowLooper; public class NetworkRequestDialogFragmentTest { private static final String KEY_SSID = "key_ssid"; + private static final String KEY_SECURITY = "key_security"; private FragmentActivity mActivity; private NetworkRequestDialogFragment networkRequestDialogFragment; private Context mContext; + private WifiTracker mWifiTracker; @Before public void setUp() { mActivity = Robolectric.setupActivity(FragmentActivity.class); networkRequestDialogFragment = spy(NetworkRequestDialogFragment.newInstance()); mContext = spy(RuntimeEnvironment.application); + + mWifiTracker = mock(WifiTracker.class); + WifiTrackerFactory.setTestingWifiTracker(mWifiTracker); } @Test @@ -140,71 +149,47 @@ public class NetworkRequestDialogFragmentTest { } @Test - public void updateAccessPointList_onUserSelectionConnectSuccess_updateCorrectly() { - List accessPointList = spy(new ArrayList<>()); - Bundle bundle = new Bundle(); - bundle.putString(KEY_SSID, "Test AP 1"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 2"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 3"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 4"); - accessPointList.add(new AccessPoint(mContext, bundle)); - + public void updateAccessPointList_onUserSelectionConnectSuccess_shouldCloseTheDialog() { + List accessPointList = createAccessPointList(); when(networkRequestDialogFragment.getAccessPointList()).thenReturn(accessPointList); networkRequestDialogFragment.show(mActivity.getSupportFragmentManager(), null); + AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(alertDialog.isShowing()).isTrue(); // Test if config would update list. WifiConfiguration config = new WifiConfiguration(); config.SSID = "Test AP 3"; networkRequestDialogFragment.onUserSelectionConnectSuccess(config); - AccessPoint verifyAccessPoint = new AccessPoint(mContext, config); - verify(accessPointList, times(1)).set(2, verifyAccessPoint); + assertThat(alertDialog.isShowing()).isFalse(); } @Test - public void updateAccessPointList_onUserSelectionConnectFailure_updateCorrectly() { - List accessPointList = spy(new ArrayList<>()); - Bundle bundle = new Bundle(); - bundle.putString(KEY_SSID, "Test AP 1"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 2"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 3"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 4"); - accessPointList.add(new AccessPoint(mContext, bundle)); + public void updateAccessPointList_onUserSelectionConnectFailure_shouldCallTimeoutDialog() { + FakeNetworkRequestDialogFragment fakeFragment = new FakeNetworkRequestDialogFragment(); + FakeNetworkRequestDialogFragment spyFakeFragment = spy(fakeFragment); + List accessPointList = createAccessPointList(); + when(spyFakeFragment.getAccessPointList()).thenReturn(accessPointList); + spyFakeFragment.show(mActivity.getSupportFragmentManager(), null); - when(networkRequestDialogFragment.getAccessPointList()).thenReturn(accessPointList); - networkRequestDialogFragment.show(mActivity.getSupportFragmentManager(), null); + AlertDialog alertDialog = ShadowAlertDialogCompat.getLatestAlertDialog(); + assertThat(alertDialog.isShowing()).isTrue(); // Test if config would update list. WifiConfiguration config = new WifiConfiguration(); config.SSID = "Test AP 3"; - networkRequestDialogFragment.onUserSelectionConnectFailure(config); + fakeFragment.onUserSelectionConnectFailure(config); - AccessPoint verifyAccessPoint = new AccessPoint(mContext, config); - verify(accessPointList, times(1)).set(2, verifyAccessPoint); + assertThat(fakeFragment.bCalledStopAndPop).isTrue(); } @Test - public void onUserSelectionCallbackRegistration_shouldCallSelect() { - List accessPointList = spy(new ArrayList<>()); - Bundle bundle = new Bundle(); - bundle.putString(KEY_SSID, "Test AP 1"); - accessPointList.add(new AccessPoint(mContext, bundle)); - bundle.putString(KEY_SSID, "Test AP 2"); - accessPointList.add(new AccessPoint(mContext, bundle)); - - bundle.putString(KEY_SSID, "Test AP 3"); - AccessPoint clickedAccessPoint = new AccessPoint(mContext, bundle); + public void onUserSelectionCallbackRegistration_onClick_shouldCallSelect() { + // Assert. + final int indexClickItem = 3; + List accessPointList = createAccessPointList(); + AccessPoint clickedAccessPoint = accessPointList.get(indexClickItem); clickedAccessPoint.generateOpenNetworkConfig(); - accessPointList.add(clickedAccessPoint); - - bundle.putString(KEY_SSID, "Test AP 4"); - accessPointList.add(new AccessPoint(mContext, bundle)); when(networkRequestDialogFragment.getAccessPointList()).thenReturn(accessPointList); NetworkRequestUserSelectionCallback selectionCallback = mock( @@ -212,40 +197,66 @@ public class NetworkRequestDialogFragmentTest { AlertDialog dialog = mock(AlertDialog.class); networkRequestDialogFragment.onUserSelectionCallbackRegistration(selectionCallback); - networkRequestDialogFragment.onClick(dialog, 2); + // Act. + networkRequestDialogFragment.onClick(dialog, indexClickItem); + // Check. verify(selectionCallback, times(1)).select(clickedAccessPoint.getConfig()); } @Test public void onMatch_shouldUpdatedList() { - // Prepares WifiManager. + // Assert. when(networkRequestDialogFragment.getContext()).thenReturn(mContext); Context applicationContext = spy(RuntimeEnvironment.application.getApplicationContext()); when(mContext.getApplicationContext()).thenReturn(applicationContext); WifiManager wifiManager = mock(WifiManager.class); when(applicationContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(wifiManager); + networkRequestDialogFragment.onResume(); + + List accessPointList = createAccessPointList(); + when(mWifiTracker.getAccessPoints()).thenReturn(accessPointList); - List wifiConfigurationList = new ArrayList<>(); - WifiConfiguration config = new WifiConfiguration(); final String SSID_AP1 = "Test AP 1"; - config.SSID = SSID_AP1; - wifiConfigurationList.add(config); - config = new WifiConfiguration(); final String SSID_AP2 = "Test AP 2"; - config.SSID = SSID_AP2; - wifiConfigurationList.add(config); - - // Prepares callback converted data. List scanResults = new ArrayList<>(); - when(wifiManager.getAllMatchingWifiConfigs(scanResults)).thenReturn(wifiConfigurationList); + ScanResult scanResult = new ScanResult(); + scanResult.SSID = SSID_AP1; + scanResult.capabilities = "WEP"; + scanResults.add(scanResult); + scanResult = new ScanResult(); + scanResult.SSID = SSID_AP2; + scanResult.capabilities = "WEP"; + scanResults.add(scanResult); + // Act. networkRequestDialogFragment.onMatch(scanResults); - List accessPointList = networkRequestDialogFragment.getAccessPointList(); - assertThat(accessPointList).isNotEmpty(); - assertThat(accessPointList.size()).isEqualTo(2); - assertThat(accessPointList.get(0).getSsid()).isEqualTo(SSID_AP1); - assertThat(accessPointList.get(1).getSsid()).isEqualTo(SSID_AP2); + // Check. + List returnList = networkRequestDialogFragment.getAccessPointList(); + assertThat(returnList).isNotEmpty(); + assertThat(returnList.size()).isEqualTo(2); + assertThat(returnList.get(0).getSsid()).isEqualTo(SSID_AP1); + assertThat(returnList.get(1).getSsid()).isEqualTo(SSID_AP2); + } + + 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); + bundle.putString(KEY_SSID, "Test AP 4"); + bundle.putInt(KEY_SECURITY, 0); + accessPointList.add(new AccessPoint(mContext, bundle)); + + return accessPointList; } } From 811d95c373f94c0aef9795634fa9de2283465165 Mon Sep 17 00:00:00 2001 From: Lifu Tang Date: Mon, 10 Dec 2018 16:38:33 -0800 Subject: [PATCH 09/19] Display recent location access in the widget Bug: 120239674 Test: manually Change-Id: Iaf899486bf27c55189eea4c0e913ff1baaf529e5 --- res/values/strings.xml | 10 +- res/xml/location_recent_requests_see_all.xml | 24 -- res/xml/location_settings.xml | 20 +- .../settings/location/LocationSettings.java | 10 +- ...entLocationAccessPreferenceController.java | 103 +++++++++ ...ntLocationRequestPreferenceController.java | 155 ------------- .../RecentLocationRequestSeeAllFragment.java | 93 -------- ...tionRequestSeeAllPreferenceController.java | 98 -------- ...cationRequestPreferenceControllerTest.java | 217 ------------------ ...RequestSeeAllPreferenceControllerTest.java | 126 ---------- 10 files changed, 117 insertions(+), 739 deletions(-) delete mode 100644 res/xml/location_recent_requests_see_all.xml create mode 100644 src/com/android/settings/location/RecentLocationAccessPreferenceController.java delete mode 100644 src/com/android/settings/location/RecentLocationRequestPreferenceController.java delete mode 100644 src/com/android/settings/location/RecentLocationRequestSeeAllFragment.java delete mode 100644 src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceController.java delete mode 100644 tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java delete mode 100644 tests/robotests/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 57897dc0ffe..4abea447d9f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3631,14 +3631,12 @@ App-level permissions - - Recent location requests - - See all + + Recent location access + + View details No apps have requested location recently - - Location services High battery use diff --git a/res/xml/location_recent_requests_see_all.xml b/res/xml/location_recent_requests_see_all.xml deleted file mode 100644 index 38db1426995..00000000000 --- a/res/xml/location_recent_requests_see_all.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index b53e9861642..f1c13cf6ecd 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -20,21 +20,14 @@ android:title="@string/location_settings_title" settings:keywords="@string/keywords_location"> - - - + + settings:initialExpandedChildrenCount="0"> + android:key="location_services" /> In switch bar: location master switch. Used to toggle location on and off. * * - *
  • Recent location requests: automatically populated by {@link RecentLocationApps}
  • + *
  • Recent location requests: automatically populated by {@link RecentLocationAccesses}
  • *
  • Location services: multi-app settings provided from outside the Android framework. Each * is injected by a system-partition app via the {@link SettingInjectorService} API.
  • * @@ -124,11 +124,9 @@ public class LocationSettings extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new AppLocationPermissionPreferenceController(context)); controllers.add(new LocationForWorkPreferenceController(context, lifecycle)); - controllers.add( - new RecentLocationRequestPreferenceController(context, fragment, lifecycle)); + controllers.add(new RecentLocationAccessPreferenceController(context)); controllers.add(new LocationScanningPreferenceController(context)); - controllers.add( - new LocationServicePreferenceController(context, fragment, lifecycle)); + controllers.add(new LocationServicePreferenceController(context, fragment, lifecycle)); controllers.add(new LocationFooterPreferenceController(context, lifecycle)); return controllers; } diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java new file mode 100644 index 00000000000..0d5cca50f99 --- /dev/null +++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 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.location; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.view.View; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.location.RecentLocationAccesses; +import com.android.settingslib.widget.AppEntitiesHeaderController; +import com.android.settingslib.widget.LayoutPreference; + +import java.util.List; + +public class RecentLocationAccessPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + /** Key for the recent location apps dashboard */ + private static final String KEY_APPS_DASHBOARD = "apps_dashboard"; + private final RecentLocationAccesses mRecentLocationAccesses; + private AppEntitiesHeaderController mController; + private static final int MAXIMUM_APP_COUNT = 3; + + public RecentLocationAccessPreferenceController(Context context) { + this(context, new RecentLocationAccesses(context)); + } + + @VisibleForTesting + RecentLocationAccessPreferenceController(Context context, + RecentLocationAccesses recentAccesses) { + super(context); + mRecentLocationAccesses = recentAccesses; + } + + @Override + public String getPreferenceKey() { + return KEY_APPS_DASHBOARD; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final LayoutPreference preference = (LayoutPreference) screen.findPreference( + KEY_APPS_DASHBOARD); + final View view = preference.findViewById(R.id.app_entities_header); + mController = AppEntitiesHeaderController.newInstance(mContext, view) + .setHeaderTitleRes(R.string.location_category_recent_location_access) + .setHeaderDetailsRes(R.string.location_recent_location_access_view_details) + .setHeaderDetailsClickListener((View v) -> { + final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE); + intent.putExtra(Intent.EXTRA_PERMISSION_NAME, + Manifest.permission.ACCESS_FINE_LOCATION); + mContext.startActivity(intent); + }); + } + + @Override + public void updateState(Preference preference) { + updateRecentApps(); + } + + private void updateRecentApps() { + final List recentLocationAccesses = + mRecentLocationAccesses.getAppListSorted(); + if (recentLocationAccesses.size() > 0) { + // Display the top 3 preferences to container in original order. + int i = 0; + for (; i < Math.min(recentLocationAccesses.size(), MAXIMUM_APP_COUNT); i++) { + final RecentLocationAccesses.Access access = recentLocationAccesses.get(i); + mController.setAppEntity(i, access.icon, access.label, access.contentDescription); + } + for (; i < MAXIMUM_APP_COUNT; i++) { + mController.removeAppEntity(i); + } + } else { + // If there's no item to display, add a "No recent apps" item. + } + mController.apply(); + } +} diff --git a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java deleted file mode 100644 index 60374eb5dda..00000000000 --- a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2017 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.location; - -import android.content.Context; -import android.os.Bundle; -import android.os.UserHandle; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.applications.appinfo.AppInfoDashboardFragment; -import com.android.settings.core.SubSettingLauncher; -import com.android.settings.dashboard.DashboardFragment; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.location.RecentLocationApps; -import com.android.settingslib.widget.apppreference.AppPreference; - -import java.util.List; - -public class RecentLocationRequestPreferenceController extends LocationBasePreferenceController { - - /** Key for preference category "Recent location requests" */ - private static final String KEY_RECENT_LOCATION_REQUESTS = "recent_location_requests"; - @VisibleForTesting - static final String KEY_SEE_ALL_BUTTON = "recent_location_requests_see_all_button"; - private final LocationSettings mFragment; - private final RecentLocationApps mRecentLocationApps; - private PreferenceCategory mCategoryRecentLocationRequests; - private Preference mSeeAllButton; - - /** Used in this class and {@link RecentLocationRequestSeeAllPreferenceController} */ - static class PackageEntryClickedListener implements Preference.OnPreferenceClickListener { - private final DashboardFragment mFragment; - private final String mPackage; - private final UserHandle mUserHandle; - - public PackageEntryClickedListener(DashboardFragment fragment, String packageName, - UserHandle userHandle) { - mFragment = fragment; - mPackage = packageName; - mUserHandle = userHandle; - } - - @Override - public boolean onPreferenceClick(Preference preference) { - // start new fragment to display extended information - final Bundle args = new Bundle(); - args.putString(AppInfoDashboardFragment.ARG_PACKAGE_NAME, mPackage); - - new SubSettingLauncher(mFragment.getContext()) - .setDestination(AppInfoDashboardFragment.class.getName()) - .setArguments(args) - .setTitleRes(R.string.application_info_label) - .setUserHandle(mUserHandle) - .setSourceMetricsCategory(mFragment.getMetricsCategory()) - .launch(); - return true; - } - } - - public RecentLocationRequestPreferenceController(Context context, LocationSettings fragment, - Lifecycle lifecycle) { - this(context, fragment, lifecycle, new RecentLocationApps(context)); - } - - @VisibleForTesting - RecentLocationRequestPreferenceController(Context context, LocationSettings fragment, - Lifecycle lifecycle, RecentLocationApps recentApps) { - super(context, lifecycle); - mFragment = fragment; - mRecentLocationApps = recentApps; - } - - @Override - public String getPreferenceKey() { - return KEY_RECENT_LOCATION_REQUESTS; - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mCategoryRecentLocationRequests = - (PreferenceCategory) screen.findPreference(KEY_RECENT_LOCATION_REQUESTS); - mSeeAllButton = screen.findPreference(KEY_SEE_ALL_BUTTON); - - } - - @Override - public void updateState(Preference preference) { - mCategoryRecentLocationRequests.removeAll(); - mSeeAllButton.setVisible(false); - - final Context prefContext = preference.getContext(); - final List recentLocationRequests = - mRecentLocationApps.getAppListSorted(); - - if (recentLocationRequests.size() > 3) { - // Display the top 3 preferences to container in original order. - for (int i = 0; i < 3; i++) { - mCategoryRecentLocationRequests.addPreference( - createAppPreference(prefContext, recentLocationRequests.get(i))); - } - // Display a button to list all requests - mSeeAllButton.setVisible(true); - } else if (recentLocationRequests.size() > 0) { - // Add preferences to container in original order (already sorted by recency). - for (RecentLocationApps.Request request : recentLocationRequests) { - mCategoryRecentLocationRequests.addPreference( - createAppPreference(prefContext, request)); - } - } else { - // If there's no item to display, add a "No recent apps" item. - final Preference banner = createAppPreference(prefContext); - banner.setTitle(R.string.location_no_recent_apps); - banner.setSelectable(false); - mCategoryRecentLocationRequests.addPreference(banner); - } - } - - @Override - public void onLocationModeChanged(int mode, boolean restricted) { - mCategoryRecentLocationRequests.setEnabled(mLocationEnabler.isEnabled(mode)); - } - - @VisibleForTesting - AppPreference createAppPreference(Context prefContext) { - return new AppPreference(prefContext); - } - - @VisibleForTesting - AppPreference createAppPreference(Context prefContext, RecentLocationApps.Request request) { - final AppPreference pref = createAppPreference(prefContext); - pref.setSummary(request.contentDescription); - pref.setIcon(request.icon); - pref.setTitle(request.label); - pref.setOnPreferenceClickListener(new PackageEntryClickedListener( - mFragment, request.packageName, request.userHandle)); - return pref; - } -} diff --git a/src/com/android/settings/location/RecentLocationRequestSeeAllFragment.java b/src/com/android/settings/location/RecentLocationRequestSeeAllFragment.java deleted file mode 100644 index d256b9b804e..00000000000 --- a/src/com/android/settings/location/RecentLocationRequestSeeAllFragment.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2018 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.location; - - -import android.content.Context; -import android.provider.SearchIndexableResource; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.R; -import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.Indexable; -import com.android.settingslib.core.AbstractPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.search.SearchIndexable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** Dashboard Fragment to display all recent location requests, sorted by recency. */ -@SearchIndexable -public class RecentLocationRequestSeeAllFragment extends DashboardFragment { - - private static final String TAG = "RecentLocationReqAll"; - - public static final String PATH = - "com.android.settings.location.RecentLocationRequestSeeAllFragment"; - - @Override - public int getMetricsCategory() { - return MetricsEvent.RECENT_LOCATION_REQUESTS_ALL; - } - - @Override - protected int getPreferenceScreenResId() { - return R.xml.location_recent_requests_see_all; - } - - @Override - protected String getLogTag() { - return TAG; - } - - @Override - protected List createPreferenceControllers(Context context) { - return buildPreferenceControllers(context, getSettingsLifecycle(), this); - } - - private static List buildPreferenceControllers( - Context context, Lifecycle lifecycle, RecentLocationRequestSeeAllFragment fragment) { - final List controllers = new ArrayList<>(); - controllers.add( - new RecentLocationRequestSeeAllPreferenceController(context, lifecycle, fragment)); - return controllers; - } - - /** - * For Search. - */ - public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getXmlResourcesToIndex( - Context context, boolean enabled) { - final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = R.xml.location_recent_requests_see_all; - return Arrays.asList(sir); - } - - @Override - public List getPreferenceControllers(Context - context) { - return buildPreferenceControllers( - context, /* lifecycle = */ null, /* fragment = */ null); - } - }; -} diff --git a/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceController.java deleted file mode 100644 index 3fa0f00e610..00000000000 --- a/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceController.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2018 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.location; - -import android.content.Context; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceScreen; - -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.location.RecentLocationApps; -import com.android.settingslib.widget.apppreference.AppPreference; - -import java.util.List; - -/** Preference controller for preference category displaying all recent location requests. */ -public class RecentLocationRequestSeeAllPreferenceController - extends LocationBasePreferenceController { - - /** Key for preference category "All recent location requests" */ - private static final String KEY_ALL_RECENT_LOCATION_REQUESTS = "all_recent_location_requests"; - private final RecentLocationRequestSeeAllFragment mFragment; - private PreferenceCategory mCategoryAllRecentLocationRequests; - private RecentLocationApps mRecentLocationApps; - - public RecentLocationRequestSeeAllPreferenceController( - Context context, Lifecycle lifecycle, RecentLocationRequestSeeAllFragment fragment) { - this(context, lifecycle, fragment, new RecentLocationApps(context)); - } - - @VisibleForTesting - RecentLocationRequestSeeAllPreferenceController( - Context context, - Lifecycle lifecycle, - RecentLocationRequestSeeAllFragment fragment, - RecentLocationApps recentLocationApps) { - super(context, lifecycle); - mFragment = fragment; - mRecentLocationApps = recentLocationApps; - } - - @Override - public String getPreferenceKey() { - return KEY_ALL_RECENT_LOCATION_REQUESTS; - } - - @Override - public void onLocationModeChanged(int mode, boolean restricted) { - mCategoryAllRecentLocationRequests.setEnabled(mLocationEnabler.isEnabled(mode)); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mCategoryAllRecentLocationRequests = - (PreferenceCategory) screen.findPreference(KEY_ALL_RECENT_LOCATION_REQUESTS); - - } - - @Override - public void updateState(Preference preference) { - mCategoryAllRecentLocationRequests.removeAll(); - List requests = mRecentLocationApps.getAppListSorted(); - for (RecentLocationApps.Request request : requests) { - Preference appPreference = createAppPreference(preference.getContext(), request); - mCategoryAllRecentLocationRequests.addPreference(appPreference); - } - } - - @VisibleForTesting - AppPreference createAppPreference( - Context prefContext, RecentLocationApps.Request request) { - final AppPreference pref = new AppPreference(prefContext); - pref.setSummary(request.contentDescription); - pref.setIcon(request.icon); - pref.setTitle(request.label); - pref.setOnPreferenceClickListener( - new RecentLocationRequestPreferenceController.PackageEntryClickedListener( - mFragment, request.packageName, request.userHandle)); - return pref; - } -} diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java deleted file mode 100644 index d4b4ac31bd1..00000000000 --- a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2017 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.location; - -import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.provider.Settings; -import android.text.TextUtils; - -import androidx.lifecycle.LifecycleOwner; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.applications.appinfo.AppInfoDashboardFragment; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.location.RecentLocationApps; -import com.android.settingslib.location.RecentLocationApps.Request; -import com.android.settingslib.widget.apppreference.AppPreference; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -public class RecentLocationRequestPreferenceControllerTest { - - @Mock - private LocationSettings mFragment; - @Mock - private PreferenceCategory mCategory; - @Mock - private PreferenceScreen mScreen; - @Mock - private RecentLocationApps mRecentLocationApps; - @Mock - private Preference mSeeAllButton; - - private Context mContext; - private RecentLocationRequestPreferenceController mController; - private LifecycleOwner mLifecycleOwner; - private Lifecycle mLifecycle; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); - mController = spy(new RecentLocationRequestPreferenceController( - mContext, mFragment, mLifecycle, mRecentLocationApps)); - when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mCategory); - when(mScreen.findPreference(mController.KEY_SEE_ALL_BUTTON)).thenReturn(mSeeAllButton); - final String key = mController.getPreferenceKey(); - when(mCategory.getKey()).thenReturn(key); - when(mCategory.getContext()).thenReturn(mContext); - } - - @Test - public void onLocationModeChanged_LocationOn_shouldEnablePreference() { - mController.displayPreference(mScreen); - - mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); - - verify(mCategory).setEnabled(true); - } - - @Test - public void onLocationModeChanged_LocationOff_shouldDisablePreference() { - mController.displayPreference(mScreen); - - mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_OFF, false); - - verify(mCategory).setEnabled(false); - } - - @Test - public void updateState_noRecentRequest_shouldRemoveAllAndAddBanner() { - doReturn(new ArrayList<>()).when(mRecentLocationApps).getAppListSorted(); - mController.displayPreference(mScreen); - - mController.updateState(mCategory); - - verify(mCategory).removeAll(); - final String title = mContext.getString(R.string.location_no_recent_apps); - verify(mCategory).addPreference(argThat(titleMatches(title))); - } - - @Test - public void updateState_hasRecentRequest_shouldRemoveAllAndAddInjectedSettings() { - List requests = createMockRequests(2); - doReturn(requests).when(mRecentLocationApps).getAppListSorted(); - - mController.displayPreference(mScreen); - mController.updateState(mCategory); - - verify(mCategory).removeAll(); - // Verifies two preferences are added in original order - InOrder inOrder = Mockito.inOrder(mCategory); - inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle0"))); - inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle1"))); - } - - @Test - public void updateState_hasOverThreeRequests_shouldDisplaySeeAllButton() { - List requests = createMockRequests(6); - when(mRecentLocationApps.getAppListSorted()).thenReturn(requests); - - mController.displayPreference(mScreen); - mController.updateState(mCategory); - - verify(mCategory).removeAll(); - // Verifies the first three preferences are added - InOrder inOrder = Mockito.inOrder(mCategory); - inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle0"))); - inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle1"))); - inOrder.verify(mCategory).addPreference(argThat(titleMatches("appTitle2"))); - verify(mCategory, never()).addPreference(argThat(titleMatches("appTitle3"))); - // Verifies the "See all" preference is visible - verify(mSeeAllButton).setVisible(true); - } - - @Test - public void createAppPreference_shouldAddClickListener() { - final Request request = mock(Request.class); - final AppPreference preference = mock(AppPreference.class); - doReturn(preference).when(mController).createAppPreference(any(Context.class)); - - mController.createAppPreference(mContext, request); - - verify(preference).setOnPreferenceClickListener( - any(RecentLocationRequestPreferenceController.PackageEntryClickedListener.class)); - } - - @Test - public void onPreferenceClick_shouldLaunchAppDetails() { - final Context context = mock(Context.class); - when(mFragment.getContext()).thenReturn(context); - - final List requests = new ArrayList<>(); - final Request request = mock(Request.class); - requests.add(request); - doReturn(requests).when(mRecentLocationApps).getAppListSorted(); - final AppPreference preference = new AppPreference(mContext); - doReturn(preference).when(mController).createAppPreference(any(Context.class)); - mController.displayPreference(mScreen); - mController.updateState(mCategory); - - final ArgumentCaptor intent = ArgumentCaptor.forClass(Intent.class); - - preference.performClick(); - - verify(context).startActivity(intent.capture()); - - assertThat(intent.getValue().getStringExtra(EXTRA_SHOW_FRAGMENT)) - .isEqualTo(AppInfoDashboardFragment.class.getName()); - } - - private static ArgumentMatcher titleMatches(String expected) { - return preference -> TextUtils.equals(expected, preference.getTitle()); - } - - private List createMockRequests(int count) { - List requests = new ArrayList<>(); - for (int i = 0; i < count; i++) { - // Add mock requests - Request req = mock(Request.class, "request" + i); - requests.add(req); - // Map mock AppPreferences with mock requests - String title = "appTitle" + i; - AppPreference appPreference = mock(AppPreference.class, "AppPreference" + i); - doReturn(title).when(appPreference).getTitle(); - doReturn(appPreference) - .when(mController).createAppPreference(any(Context.class), eq(req)); - } - return requests; - } -} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceControllerTest.java deleted file mode 100644 index 7411afe1b4b..00000000000 --- a/tests/robotests/src/com/android/settings/location/RecentLocationRequestSeeAllPreferenceControllerTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2018 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.location; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.provider.Settings.Secure; - -import androidx.lifecycle.LifecycleOwner; -import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceScreen; - -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.location.RecentLocationApps; -import com.android.settingslib.location.RecentLocationApps.Request; -import com.android.settingslib.widget.apppreference.AppPreference; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.ArrayList; -import java.util.Collections; - -/** Unit tests for {@link RecentLocationRequestSeeAllPreferenceController} */ -@RunWith(RobolectricTestRunner.class) -public class RecentLocationRequestSeeAllPreferenceControllerTest { - - @Mock - RecentLocationRequestSeeAllFragment mFragment; - @Mock - private PreferenceScreen mScreen; - @Mock - private PreferenceCategory mCategory; - @Mock - private RecentLocationApps mRecentLocationApps; - - private Context mContext; - private LifecycleOwner mLifecycleOwner; - private Lifecycle mLifecycle; - private RecentLocationRequestSeeAllPreferenceController mController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); - mController = spy( - new RecentLocationRequestSeeAllPreferenceController( - mContext, mLifecycle, mFragment, mRecentLocationApps)); - when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mCategory); - final String key = mController.getPreferenceKey(); - when(mCategory.getKey()).thenReturn(key); - when(mCategory.getContext()).thenReturn(mContext); - } - - @Test - public void onLocationModeChanged_locationOn_shouldEnablePreference() { - mController.displayPreference(mScreen); - - mController.onLocationModeChanged(Secure.LOCATION_MODE_HIGH_ACCURACY, false); - - verify(mCategory).setEnabled(true); - } - - @Test - public void onLocationModeChanged_locationOff_shouldDisablePreference() { - mController.displayPreference(mScreen); - - mController.onLocationModeChanged(Secure.LOCATION_MODE_OFF, false); - - verify(mCategory).setEnabled(false); - } - - @Test - public void updateState_shouldRemoveAll() { - doReturn(Collections.EMPTY_LIST).when(mRecentLocationApps).getAppListSorted(); - - mController.displayPreference(mScreen); - mController.updateState(mCategory); - - verify(mCategory).removeAll(); - } - - @Test - public void updateState_hasRecentLocationRequest_shouldAddPreference() { - Request request = mock(Request.class); - AppPreference appPreference = mock(AppPreference.class); - doReturn(appPreference) - .when(mController).createAppPreference(any(Context.class), eq(request)); - when(mRecentLocationApps.getAppListSorted()) - .thenReturn(new ArrayList<>(Collections.singletonList(request))); - - mController.displayPreference(mScreen); - mController.updateState(mCategory); - - verify(mCategory).removeAll(); - verify(mCategory).addPreference(appPreference); - } -} From 3da8f8d31dc78f39cc598cce6b0c9012da1e64af Mon Sep 17 00:00:00 2001 From: Lifu Tang Date: Tue, 11 Dec 2018 13:50:34 -0800 Subject: [PATCH 10/19] Display app stats for location permission Bug: 120221631 Test: manually Change-Id: I53f43079807759c50eeb62029bb0d8d1f84e1118 --- res/values/strings.xml | 28 +++++- res/xml/top_level_settings.xml | 7 +- ...ocationPermissionPreferenceController.java | 77 ++++++++++++++- .../settings/location/LocationEnabler.java | 16 ++- .../settings/location/LocationSettings.java | 2 +- .../TopLevelLocationPreferenceController.java | 99 +++++++++++++++++++ ...ionPermissionPreferenceControllerTest.java | 11 ++- .../location/LocationEnablerTest.java | 15 +-- 8 files changed, 226 insertions(+), 29 deletions(-) create mode 100644 src/com/android/settings/location/TopLevelLocationPreferenceController.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 4abea447d9f..be9cc14754e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -827,8 +827,15 @@ Location Use location - - Scanning, location history + + Off + + + On - %1$d app can access location + On - %1$d apps can access location + + + Loading\u2026 Accounts @@ -3630,7 +3637,22 @@ Location for work profile - App-level permissions + App permission + + Location is off + + + + %1$d + of + %2$d + app has unlimited access + + %1$d + of + %2$d + apps have unlimited access + Recent location access diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index 03e32dcfb27..9f4f90279f8 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -93,10 +93,11 @@ + android:fragment="com.android.settings.location.LocationSettings" + settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/> - \ No newline at end of file + diff --git a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java index f920fdc7dc0..5bfc58447e1 100644 --- a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java +++ b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java @@ -1,18 +1,38 @@ package com.android.settings.location; +import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + import android.content.Context; +import android.location.LocationManager; +import android.permission.RuntimePermissionPresenter; import android.provider.Settings; +import androidx.preference.Preference; + +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; public class AppLocationPermissionPreferenceController extends - AbstractPreferenceController implements PreferenceControllerMixin { + LocationBasePreferenceController implements PreferenceControllerMixin { private static final String KEY_APP_LEVEL_PERMISSIONS = "app_level_permissions"; + /** Total number of apps that has location permission. */ + private int mNumTotal = -1; + /** Total number of apps that has background location permission. */ + private int mNumBackground = -1; + private final LocationManager mLocationManager; + private Preference mPreference; - public AppLocationPermissionPreferenceController(Context context) { - super(context); + public AppLocationPermissionPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } @Override @@ -25,4 +45,53 @@ public class AppLocationPermissionPreferenceController extends return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED, 1) == 1; } + + @Override + public CharSequence getSummary() { + if (mLocationManager.isLocationEnabled()) { + if (mNumTotal == -1 || mNumBackground == -1) { + return mContext.getString(R.string.location_settings_loading_app_permission_stats); + } + return mContext.getResources().getQuantityString( + R.plurals.location_app_permission_summary_location_on, mNumBackground, + mNumBackground, mNumTotal); + } else { + return mContext.getString(R.string.location_app_permission_summary_location_off); + } + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mPreference = preference; + final AtomicInteger loadingInProgress = new AtomicInteger(2); + refreshSummary(preference); + // Bail out if location has been disabled. + if (!mLocationManager.isLocationEnabled()) { + return; + } + RuntimePermissionPresenter.getInstance(mContext).countPermissionApps( + Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), false, false, + (numApps) -> { + mNumTotal = numApps; + if (loadingInProgress.decrementAndGet() == 0) { + refreshSummary(preference); + } + }, null); + + RuntimePermissionPresenter.getInstance(mContext).countPermissionApps( + Collections.singletonList(ACCESS_BACKGROUND_LOCATION), true, false, + (numApps) -> { + mNumBackground = numApps; + if (loadingInProgress.decrementAndGet() == 0) { + refreshSummary(preference); + } + }, null); + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + // 'null' is checked inside updateState(), so no need to check here. + updateState(mPreference); + } } diff --git a/src/com/android/settings/location/LocationEnabler.java b/src/com/android/settings/location/LocationEnabler.java index 20c228024ff..e1bdf162ef2 100644 --- a/src/com/android/settings/location/LocationEnabler.java +++ b/src/com/android/settings/location/LocationEnabler.java @@ -35,15 +35,15 @@ import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnPause; -import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; /** * A class that listens to location settings change and modifies location settings * settings. */ -public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { +public class LocationEnabler implements LifecycleObserver, OnStart, OnStop { private static final String TAG = "LocationEnabler"; @VisibleForTesting @@ -73,7 +73,7 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { } @Override - public void onResume() { + public void onStart() { if (mReceiver == null) { mReceiver = new BroadcastReceiver() { @Override @@ -90,12 +90,8 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { } @Override - public void onPause() { - try { - mContext.unregisterReceiver(mReceiver); - } catch (RuntimeException e) { - // Ignore exceptions caused by race condition - } + public void onStop() { + mContext.unregisterReceiver(mReceiver); } void refreshLocationMode() { diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 8a92f4706eb..41123402039 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -122,7 +122,7 @@ public class LocationSettings extends DashboardFragment { private static List buildPreferenceControllers( Context context, LocationSettings fragment, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); - controllers.add(new AppLocationPermissionPreferenceController(context)); + controllers.add(new AppLocationPermissionPreferenceController(context, lifecycle)); controllers.add(new LocationForWorkPreferenceController(context, lifecycle)); controllers.add(new RecentLocationAccessPreferenceController(context)); controllers.add(new LocationScanningPreferenceController(context)); diff --git a/src/com/android/settings/location/TopLevelLocationPreferenceController.java b/src/com/android/settings/location/TopLevelLocationPreferenceController.java new file mode 100644 index 00000000000..d0bd9a92843 --- /dev/null +++ b/src/com/android/settings/location/TopLevelLocationPreferenceController.java @@ -0,0 +1,99 @@ +package com.android.settings.location; + +import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.LocationManager; +import android.permission.RuntimePermissionPresenter; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.util.Arrays; +import java.util.Collections; + +public class TopLevelLocationPreferenceController extends BasePreferenceController implements + LifecycleObserver, OnStart, OnStop { + private static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED = + new IntentFilter(LocationManager.MODE_CHANGED_ACTION); + private final LocationManager mLocationManager; + /** Total number of apps that has location permission. */ + private int mNumTotal = -1; + private BroadcastReceiver mReceiver; + private Preference mPreference; + + public TopLevelLocationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + if (mLocationManager.isLocationEnabled()) { + if (mNumTotal == -1) { + return mContext.getString(R.string.location_settings_loading_app_permission_stats); + } + return mContext.getResources().getQuantityString( + R.plurals.location_settings_summary_location_on, + mNumTotal, mNumTotal); + } else { + return mContext.getString(R.string.location_settings_summary_location_off); + } + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mPreference = preference; + refreshSummary(preference); + // Bail out if location has been disabled. + if (!mLocationManager.isLocationEnabled()) { + return; + } + RuntimePermissionPresenter.getInstance(mContext).countPermissionApps( + Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), false, false, + (numApps) -> { + mNumTotal = numApps; + refreshSummary(preference); + }, null); + } + + @Override + public void onStart() { + if (mReceiver == null) { + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + refreshLocationMode(); + } + }; + } + mContext.registerReceiver(mReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED); + refreshLocationMode(); + } + + @Override + public void onStop() { + mContext.unregisterReceiver(mReceiver); + } + + private void refreshLocationMode() { + // 'null' is checked inside updateState(), so no need to check here. + updateState(mPreference); + } +} diff --git a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java index eff5d4337ed..6379e445f4b 100644 --- a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java @@ -5,6 +5,10 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.provider.Settings; +import androidx.lifecycle.LifecycleOwner; + +import com.android.settingslib.core.lifecycle.Lifecycle; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,11 +25,16 @@ public class AppLocationPermissionPreferenceControllerTest { @Mock private Context mContext; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mController = new AppLocationPermissionPreferenceController(mContext); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + mController = new AppLocationPermissionPreferenceController(mContext, mLifecycle); } @Test diff --git a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java index e3808305982..806e2ecf980 100644 --- a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java @@ -84,30 +84,31 @@ public class LocationEnablerTest { } @Test - public void onResume_shouldSetActiveAndRegisterListener() { - mEnabler.onResume(); + public void onStart_shouldSetActiveAndRegisterListener() { + mEnabler.onStart(); verify(mContext).registerReceiver(eq(mEnabler.mReceiver), eq(LocationEnabler.INTENT_FILTER_LOCATION_MODE_CHANGED)); } @Test - public void onResume_shouldRefreshLocationMode() { - mEnabler.onResume(); + public void onStart_shouldRefreshLocationMode() { + mEnabler.onStart(); verify(mEnabler).refreshLocationMode(); } @Test - public void onPause_shouldUnregisterListener() { - mEnabler.onPause(); + public void onStop_shouldUnregisterListener() { + mEnabler.onStart(); + mEnabler.onStop(); verify(mContext).unregisterReceiver(mEnabler.mReceiver); } @Test public void onReceive_shouldRefreshLocationMode() { - mEnabler.onResume(); + mEnabler.onStart(); reset(mListener); mEnabler.mReceiver.onReceive(mContext, new Intent()); From 39dcad9bd10c5f63222d0a45140e77314b791eae Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Fri, 7 Dec 2018 20:13:56 +0800 Subject: [PATCH 11/19] Don't have cards auto-filled in the foreground when a card gets dismissed. Currently if users dismiss a card or navigate back to the homepage, new suggestions will be auto filled in. We should only add new cards upon next visit. Also fix suggestion cards undismissable problem. Fixes: 121175037 Bug: 120628661 Test: robotests Change-Id: I01d74aaaa21c8408e5cecafef04a7d52c97bccc5 --- .../ContextualCardManager.java | 59 ++++++++++++++- .../ContextualCardsFragment.java | 3 +- .../ContextualCardManagerTest.java | 73 ++++++++++++++++++- 3 files changed, 128 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java index 24266eea409..9c06beb7ebc 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java @@ -35,6 +35,7 @@ import androidx.loader.content.Loader; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState; import java.util.ArrayList; import java.util.List; @@ -56,7 +57,9 @@ import java.util.stream.Collectors; * get the page refreshed. */ public class ContextualCardManager implements ContextualCardLoader.CardContentLoaderListener, - ContextualCardUpdateListener { + ContextualCardUpdateListener, LifecycleObserver, OnSaveInstanceState { + + private static final String KEY_CONTEXTUAL_CARDS = "key_contextual_cards"; private static final String TAG = "ContextualCardManager"; @@ -68,6 +71,9 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo final List mContextualCards; @VisibleForTesting long mStartTime; + boolean mIsFirstLaunch; + @VisibleForTesting + List mSavedCards; private final Context mContext; private final ControllerRendererPool mControllerRendererPool; @@ -76,12 +82,20 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo private ContextualCardUpdateListener mListener; - public ContextualCardManager(Context context, Lifecycle lifecycle) { + public ContextualCardManager(Context context, Lifecycle lifecycle, Bundle savedInstanceState) { mContext = context; mLifecycle = lifecycle; mContextualCards = new ArrayList<>(); mLifecycleObservers = new ArrayList<>(); mControllerRendererPool = new ControllerRendererPool(); + mLifecycle.addObserver(this); + + if (savedInstanceState == null) { + mIsFirstLaunch = true; + mSavedCards = null; + } else { + mSavedCards = savedInstanceState.getStringArrayList(KEY_CONTEXTUAL_CARDS); + } //for data provided by Settings for (@ContextualCard.CardType int cardType : SETTINGS_CARDS) { setupController(cardType); @@ -172,13 +186,34 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo @Override public void onFinishCardLoading(List cards) { final long loadTime = System.currentTimeMillis() - mStartTime; + final List cardsToKeep = getCardsToKeep(cards); + + //navigate back to the homepage or after card dismissal + if (!mIsFirstLaunch) { + onContextualCardUpdated(cardsToKeep.stream() + .collect(groupingBy(ContextualCard::getCardType))); + return; + } + + //only log homepage display upon a fresh launch if (loadTime <= ContextualCardLoader.CARD_CONTENT_LOADER_TIMEOUT_MS) { - onContextualCardUpdated( - cards.stream().collect(groupingBy(ContextualCard::getCardType))); + onContextualCardUpdated(cards.stream() + .collect(groupingBy(ContextualCard::getCardType))); } final long totalTime = System.currentTimeMillis() - mStartTime; FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider() .logHomepageDisplay(mContext, totalTime); + + mIsFirstLaunch = false; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + final ArrayList cards = mContextualCards.stream() + .map(ContextualCard::getName) + .collect(Collectors.toCollection(ArrayList::new)); + + outState.putStringArrayList(KEY_CONTEXTUAL_CARDS, cards); } public ControllerRendererPool getControllerRendererPool() { @@ -189,6 +224,22 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo mListener = listener; } + private List getCardsToKeep(List cards) { + if (mSavedCards != null) { + //screen rotate + final List cardsToKeep = cards.stream() + .filter(card -> mSavedCards.contains(card.getName())) + .collect(Collectors.toList()); + mSavedCards = null; + return cardsToKeep; + } else { + //navigate back to the homepage or after dismissing a card + return cards.stream() + .filter(card -> mContextualCards.contains(card)) + .collect(Collectors.toList()); + } + } + static class CardContentLoaderCallbacks implements LoaderManager.LoaderCallbacks> { diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java index 5f9bf0df6ac..e598e4c6946 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java @@ -42,7 +42,8 @@ public class ContextualCardsFragment extends InstrumentedFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mContextualCardManager = new ContextualCardManager(getContext(), getSettingsLifecycle()); + mContextualCardManager = new ContextualCardManager(getContext(), getSettingsLifecycle(), + savedInstanceState); } @Override diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java index 2af84f96ca8..c405ffc4f1b 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java @@ -42,13 +42,16 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @RunWith(RobolectricTestRunner.class) public class ContextualCardManagerTest { private static final String TEST_SLICE_URI = "context://test/test"; + private static final String TEST_SLICE_NAME = "test_name"; @Mock ContextualCardUpdateListener mListener; @@ -61,7 +64,8 @@ public class ContextualCardManagerTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; final ContextualCardsFragment fragment = new ContextualCardsFragment(); - mManager = new ContextualCardManager(mContext, fragment.getSettingsLifecycle()); + mManager = new ContextualCardManager(mContext, fragment.getSettingsLifecycle(), + null /* bundle */); } @Test @@ -135,9 +139,74 @@ public class ContextualCardManagerTest { verify(manager, never()).onContextualCardUpdated(anyMap()); } + @Test + public void onFinishCardLoading_newLaunch_twoLoadedCards_shouldShowTwoCards() { + mManager.mStartTime = System.currentTimeMillis(); + mManager.setListener(mListener); + final List cards = new ArrayList<>(); + cards.add(buildContextualCard(TEST_SLICE_URI)); + cards.add(buildContextualCard(TEST_SLICE_URI)); + + mManager.onFinishCardLoading(cards); + + assertThat(mManager.mContextualCards).hasSize(2); + } + + @Test + public void onFinishCardLoading_hasSavedCard_shouldOnlyShowSavedCard() { + mManager.setListener(mListener); + final List savedCardNames = new ArrayList<>(); + savedCardNames.add(TEST_SLICE_NAME); + mManager.mIsFirstLaunch = false; + mManager.mSavedCards = savedCardNames; + final ContextualCard newCard = + new ContextualCard.Builder() + .setName("test_name2") + .setCardType(ContextualCard.CardType.SLICE) + .setSliceUri(Uri.parse("content://test/test2")) + .build(); + final List loadedCards = new ArrayList<>(); + loadedCards.add(buildContextualCard(TEST_SLICE_URI)); + loadedCards.add(newCard); + + mManager.onFinishCardLoading(loadedCards); + + final List actualCards = mManager.mContextualCards.stream() + .map(ContextualCard::getName) + .collect(Collectors.toList()); + final List expectedCards = Arrays.asList(TEST_SLICE_NAME); + assertThat(actualCards).containsExactlyElementsIn(expectedCards); + } + + @Test + public void onFinishCardLoading_reloadData_shouldOnlyShowOldCard() { + mManager.setListener(mListener); + mManager.mIsFirstLaunch = false; + //old card + mManager.mContextualCards.add(buildContextualCard(TEST_SLICE_URI)); + final ContextualCard newCard = + new ContextualCard.Builder() + .setName("test_name2") + .setCardType(ContextualCard.CardType.SLICE) + .setSliceUri(Uri.parse("content://test/test2")) + .build(); + final List loadedCards = new ArrayList<>(); + loadedCards.add(buildContextualCard(TEST_SLICE_URI)); + loadedCards.add(newCard); + + mManager.onFinishCardLoading(loadedCards); + + final List actualCards = mManager.mContextualCards.stream() + .map(ContextualCard::getName) + .collect(Collectors.toList()); + final List expectedCards = Arrays.asList(TEST_SLICE_NAME); + assertThat(actualCards).containsExactlyElementsIn(expectedCards); + } + private ContextualCard buildContextualCard(String sliceUri) { return new ContextualCard.Builder() - .setName("test_name") + .setName(TEST_SLICE_NAME) + .setCardType(ContextualCard.CardType.SLICE) .setSliceUri(Uri.parse(sliceUri)) .build(); } From f109282c8ea955bfc72a025c7068930c67c38681 Mon Sep 17 00:00:00 2001 From: Anton Hansson Date: Mon, 10 Dec 2018 18:14:48 +0000 Subject: [PATCH 12/19] Move Settings app to /product Since this app is frequently customized by OEMs, it doesn't fit in the mainline /system image right now. Move it to /product. Bug: 110072687 Test: make Change-Id: Iee5940125af3f2a8543ecb3a68d7fba737acd77d --- Android.mk | 1 + CleanSpec.mk | 1 + 2 files changed, 2 insertions(+) diff --git a/Android.mk b/Android.mk index 0fbaacfa157..c99e30c58eb 100644 --- a/Android.mk +++ b/Android.mk @@ -14,6 +14,7 @@ include $(CLEAR_VARS) LOCAL_PACKAGE_NAME := Settings LOCAL_PRIVATE_PLATFORM_APIS := true LOCAL_CERTIFICATE := platform +LOCAL_PRODUCT_MODULE := true LOCAL_PRIVILEGED_MODULE := true LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.settings LOCAL_MODULE_TAGS := optional diff --git a/CleanSpec.mk b/CleanSpec.mk index 6ead46ebd24..93445f949d0 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -46,6 +46,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Settings_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Settings_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Settings) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST From a42a93bbfede22876cb8c59a4feb0ed9af1f5c9e Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 18 Dec 2018 18:27:50 +0800 Subject: [PATCH 13/19] Fix some pages crash after importing AndroidX from build 5175906 - Settings crash while entering Storage, Privacy, and Accounts page, because PreferenceGroup changed the API use from String#equals to String#contentEquals which doesn't support null keys. - add a null check before calling findPreference Test: robotest Change-Id: I121cd9e4249fbdafbc67be65a09d770603e01044 Fixes: 121116425 --- src/com/android/settings/dashboard/DashboardFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 56e9ee53cf6..19161109c82 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -295,7 +295,13 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment if (!controller.isAvailable()) { continue; } + final String key = controller.getPreferenceKey(); + if (TextUtils.isEmpty(key)) { + Log.d(TAG, String.format("Preference key is %s in Controller %s", + key, controller.getClass().getSimpleName())); + continue; + } final Preference preference = screen.findPreference(key); if (preference == null) { From 2fba1943fa9249c182ad3f1b800d4d79f5d94527 Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Mon, 17 Dec 2018 16:58:25 -0800 Subject: [PATCH 14/19] Fix bug of not showing locked icon in WifiSettings This bug only exist by launching WifiSettings from quick settings. Quick settings launch this page by intent, where we use Theme.Settings. However we only have frictionIconColor in Theme.SubSettings, not Theme.Settings. This CL move related attr to correct theme, so we won't show invisible icon anymore. Bug: 120710149 Test: Manual Change-Id: Iae07f6ca19eda0bfbc352f03787b5a583c4066f2 --- res/values/themes.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/res/values/themes.xml b/res/values/themes.xml index 390be584e21..e972ea728ab 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -32,9 +32,13 @@ @style/FingerprintLayoutTheme @style/FaceLayoutTheme @*android:drawable/ic_menu_moreoverflow_holo_dark + + @drawable/wifi_signal ?android:attr/colorAccent @drawable/wifi_friction + ?android:colorControlNormal + @dimen/settings_side_margin ?android:attr/colorAccent @@ -71,9 +75,6 @@ @style/Widget.ActionBar.SubSettings @style/ThemeOverlay.SwitchBar.Settings - - - ?android:colorControlNormal + + diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml index 0f7ecabea18..f25289df748 100644 --- a/res/values/styles_preference.xml +++ b/res/values/styles_preference.xml @@ -21,6 +21,7 @@ @@ -34,6 +35,10 @@ @layout/apn_preference_layout + + diff --git a/res/values/themes.xml b/res/values/themes.xml index 390be584e21..e06c362708d 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -58,6 +58,9 @@ @*android:color/primary_device_default_settings_light @android:color/white + + + @style/Widget.SliceView.Settings diff --git a/res/xml/bluetooth_device_details_fragment.xml b/res/xml/bluetooth_device_details_fragment.xml index 40ce93db152..90895f258fe 100644 --- a/res/xml/bluetooth_device_details_fragment.xml +++ b/res/xml/bluetooth_device_details_fragment.xml @@ -26,7 +26,14 @@ settings:allowDividerBelow="true"/> + android:key="action_buttons" + settings:allowDividerBelow="true"/> + + diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java index 6557aee26cb..fdd7f2e46b3 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -32,7 +32,6 @@ import com.android.settings.display.TapToWakePreferenceController; import com.android.settings.display.ThemePreferenceController; import com.android.settings.display.TimeoutPreferenceController; import com.android.settings.display.VrDisplayPreferenceController; -import com.android.settings.display.WallpaperPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settingslib.core.AbstractPreferenceController; diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index df321118c1e..a143d787872 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -19,14 +19,19 @@ package com.android.settings.bluetooth; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import android.bluetooth.BluetoothDevice; +import android.content.ContentResolver; import android.content.Context; +import android.net.Uri; import android.os.Bundle; +import android.util.FeatureFlagUtils; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.core.FeatureFlags; import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.slices.SlicePreferenceController; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.AbstractPreferenceController; @@ -98,6 +103,15 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment mManager = getLocalBluetoothManager(context); mCachedDevice = getCachedDevice(mDeviceAddress); super.onAttach(context); + + if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) { + //TODO(b/120803703): update it to get data from feature provider + use(SlicePreferenceController.class).setSliceUri(new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority("com.google.android.apps.wearables.maestro.companion") + .appendPath("setting_slice") + .build()); + } } @Override diff --git a/src/com/android/settings/slices/SlicePreference.java b/src/com/android/settings/slices/SlicePreference.java new file mode 100644 index 00000000000..98719f7de8c --- /dev/null +++ b/src/com/android/settings/slices/SlicePreference.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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.slices; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.slice.Slice; +import androidx.slice.widget.SliceView; + +import com.android.settings.R; +import com.android.settingslib.widget.LayoutPreference; + +/** + * Preference for {@link SliceView} + */ +public class SlicePreference extends LayoutPreference { + private SliceView mSliceView; + + public SlicePreference(Context context, AttributeSet attrs) { + super(context, attrs, R.attr.slicePreferenceStyle); + mSliceView = findViewById(R.id.slice_view); + } + + public SlicePreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, R.attr.slicePreferenceStyle); + mSliceView = findViewById(R.id.slice_view); + } + + public void onSliceUpdated(Slice slice) { + mSliceView.onChanged(slice); + notifyChanged(); + } +} diff --git a/src/com/android/settings/slices/SlicePreferenceController.java b/src/com/android/settings/slices/SlicePreferenceController.java new file mode 100644 index 00000000000..8c751c8f609 --- /dev/null +++ b/src/com/android/settings/slices/SlicePreferenceController.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018 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.slices; + +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; +import androidx.preference.PreferenceScreen; +import androidx.slice.Slice; +import androidx.slice.widget.SliceLiveData; +import androidx.slice.widget.SliceView; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +/** + * Default {@link BasePreferenceController} for {@link SliceView}. It will take {@link Uri} for + * Slice and display what's inside this {@link Uri} + */ +public class SlicePreferenceController extends BasePreferenceController implements + LifecycleObserver, OnStart, OnStop, Observer { + @VisibleForTesting + LiveData mLiveData; + private SlicePreference mSlicePreference; + private Uri mUri; + + public SlicePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mSlicePreference = (SlicePreference) screen.findPreference( + getPreferenceKey()); + } + + @Override + public int getAvailabilityStatus() { + return mUri != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + public void setSliceUri(Uri uri) { + mUri = uri; + mLiveData = SliceLiveData.fromUri(mContext, mUri); + + //TODO(b/120803703): figure out why we need to remove observer first + mLiveData.removeObserver(this); + } + + @Override + public void onStart() { + if (mLiveData != null) { + mLiveData.observeForever(this); + } + } + + @Override + public void onStop() { + if (mLiveData != null) { + mLiveData.removeObserver(this); + } + } + + @Override + public void onChanged(Slice slice) { + mSlicePreference.onSliceUpdated(slice); + } +} diff --git a/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java new file mode 100644 index 00000000000..364fb60947b --- /dev/null +++ b/tests/robotests/src/com/android/settings/slices/SlicePreferenceControllerTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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.slices; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.Uri; + +import androidx.lifecycle.LiveData; +import androidx.slice.Slice; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class SlicePreferenceControllerTest { + private static final String KEY = "slice_preference_key"; + + @Mock + private LiveData mLiveData; + private Context mContext; + private SlicePreferenceController mController; + private Uri mUri; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + mController = new SlicePreferenceController(mContext, KEY); + mController.mLiveData = mLiveData; + mUri = Uri.EMPTY; + } + + @Test + public void isAvailable_uriNull_returnFalse() { + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_uriNotNull_returnTrue() { + mController.setSliceUri(mUri); + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void onStart_registerObserver() { + mController.onStart(); + verify(mLiveData).observeForever(mController); + } + + @Test + public void onStop_unregisterObserver() { + mController.onStop(); + verify(mLiveData).removeObserver(mController); + } +} \ No newline at end of file From bdc4ea63369d80b54c2178d98a4ec974ee75533f Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Tue, 18 Dec 2018 13:03:46 -0800 Subject: [PATCH 19/19] Add feature provider for bluetooth settings Also add method to get settings uri for specific device. Use feature provider here because it give us more flexibility. Bug: 120803703 Test: RunSettingsRoboTests Change-Id: I6f4840e76279c02a75b95fdecd822a72cb0b42e5 --- res/values/config.xml | 3 ++ .../BluetoothDeviceDetailsFragment.java | 13 ++---- .../bluetooth/BluetoothFeatureProvider.java | 32 +++++++++++++ .../BluetoothFeatureProviderImpl.java | 41 +++++++++++++++++ .../settings/overlay/FeatureFactory.java | 3 ++ .../settings/overlay/FeatureFactoryImpl.java | 12 +++++ .../BluetoothFeatureProviderImplTest.java | 46 +++++++++++++++++++ .../testutils/FakeFeatureFactory.java | 8 ++++ 8 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 src/com/android/settings/bluetooth/BluetoothFeatureProvider.java create mode 100644 src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java diff --git a/res/values/config.xml b/res/values/config.xml index edd948f7962..7108cbde50e 100755 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -165,4 +165,7 @@ + + + content://com.google.android.gms.nearby.fastpair/settings_slice?addr=%1$s diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java index a143d787872..6ec419b6a8c 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragment.java @@ -19,9 +19,7 @@ package com.android.settings.bluetooth; import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; import android.bluetooth.BluetoothDevice; -import android.content.ContentResolver; import android.content.Context; -import android.net.Uri; import android.os.Bundle; import android.util.FeatureFlagUtils; @@ -31,6 +29,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.core.FeatureFlags; import com.android.settings.dashboard.RestrictedDashboardFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.slices.SlicePreferenceController; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -105,12 +104,10 @@ public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment super.onAttach(context); if (FeatureFlagUtils.isEnabled(context, FeatureFlags.SLICE_INJECTION)) { - //TODO(b/120803703): update it to get data from feature provider - use(SlicePreferenceController.class).setSliceUri(new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority("com.google.android.apps.wearables.maestro.companion") - .appendPath("setting_slice") - .build()); + final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(context) + .getBluetoothFeatureProvider(context); + use(SlicePreferenceController.class).setSliceUri( + featureProvider.getBluetoothDeviceSettingsUri(mDeviceAddress)); } } diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java new file mode 100644 index 00000000000..2bca03808b1 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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.bluetooth; + +import android.net.Uri; + +/** + * Provider for bluetooth related feature + */ +public interface BluetoothFeatureProvider { + + /** + * Get the {@link Uri} that represents extra settings for a specific bluetooth device + * @param macAddress Bluetooth mac address + * @return {@link Uri} for extra settings + */ + Uri getBluetoothDeviceSettingsUri(String macAddress); +} diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java new file mode 100644 index 00000000000..dcdc2fd77b0 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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.bluetooth; + +import android.content.Context; +import android.net.Uri; + +import com.android.settings.R; + +/** + * Impl of {@link BluetoothFeatureProvider} + */ +public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider { + + private Context mContext; + + public BluetoothFeatureProviderImpl(Context context) { + mContext = context; + } + + @Override + public Uri getBluetoothDeviceSettingsUri(String macAddress) { + final String uriString = mContext.getString(R.string.config_bluetooth_device_settings_uri, + macAddress); + return Uri.parse(uriString); + } +} diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 184ccc8b7ab..76962b2658a 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -24,6 +24,7 @@ import com.android.settings.R; import com.android.settings.accounts.AccountFeatureProvider; import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.biometrics.face.FaceFeatureProvider; +import com.android.settings.bluetooth.BluetoothFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; @@ -114,6 +115,8 @@ public abstract class FeatureFactory { public abstract FaceFeatureProvider getFaceFeatureProvider(); + public abstract BluetoothFeatureProvider getBluetoothFeatureProvider(Context context); + public static final class FactoryNotFoundException extends RuntimeException { public FactoryNotFoundException(Throwable throwable) { super("Unable to create factory. Did you misconfigure Proguard?", throwable); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index 88c09d036a2..50b9f8f3e50 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -30,6 +30,8 @@ import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.applications.ApplicationFeatureProviderImpl; import com.android.settings.biometrics.face.FaceFeatureProvider; import com.android.settings.biometrics.face.FaceFeatureProviderImpl; +import com.android.settings.bluetooth.BluetoothFeatureProvider; +import com.android.settings.bluetooth.BluetoothFeatureProviderImpl; import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl; import com.android.settings.core.instrumentation.SettingsMetricsFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; @@ -81,6 +83,7 @@ public class FeatureFactoryImpl extends FeatureFactory { private PanelFeatureProvider mPanelFeatureProvider; private ContextualCardFeatureProvider mContextualCardFeatureProvider; private FaceFeatureProvider mFaceFeatureProvider; + private BluetoothFeatureProvider mBluetoothFeatureProvider; @Override public SupportFeatureProvider getSupportFeatureProvider(Context context) { @@ -242,4 +245,13 @@ public class FeatureFactoryImpl extends FeatureFactory { } return mFaceFeatureProvider; } + + @Override + public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) { + if (mBluetoothFeatureProvider == null) { + mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl( + context.getApplicationContext()); + } + return mBluetoothFeatureProvider; + } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java new file mode 100644 index 00000000000..887f58c3245 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFeatureProviderImplTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothFeatureProviderImplTest { + private static final String PARAMETER_KEY = "addr"; + private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; + private BluetoothFeatureProvider mBluetoothFeatureProvider; + + @Before + public void setUp() { + mBluetoothFeatureProvider = new BluetoothFeatureProviderImpl( + RuntimeEnvironment.application); + } + + @Test + public void getBluetoothDeviceSettingsUri_containCorrectMacAddress() { + final Uri uri = mBluetoothFeatureProvider.getBluetoothDeviceSettingsUri(MAC_ADDRESS); + assertThat(uri.getQueryParameterNames()).containsExactly(PARAMETER_KEY); + assertThat(uri.getQueryParameter(PARAMETER_KEY)).isEqualTo(MAC_ADDRESS); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java index e0b8646df25..a864bf6e561 100644 --- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java +++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java @@ -24,6 +24,7 @@ import android.content.Context; import com.android.settings.accounts.AccountFeatureProvider; import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.biometrics.face.FaceFeatureProvider; +import com.android.settings.bluetooth.BluetoothFeatureProvider; import com.android.settings.dashboard.DashboardFeatureProvider; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider; @@ -66,6 +67,7 @@ public class FakeFeatureFactory extends FeatureFactory { public final AccountFeatureProvider mAccountFeatureProvider; public final ContextualCardFeatureProvider mContextualCardFeatureProvider; public final FaceFeatureProvider mFaceFeatureProvider; + public final BluetoothFeatureProvider mBluetoothFeatureProvider; public PanelFeatureProvider panelFeatureProvider; public SlicesFeatureProvider slicesFeatureProvider; @@ -111,6 +113,7 @@ public class FakeFeatureFactory extends FeatureFactory { mContextualCardFeatureProvider = mock(ContextualCardFeatureProvider.class); panelFeatureProvider = mock(PanelFeatureProvider.class); mFaceFeatureProvider = mock(FaceFeatureProvider.class); + mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class); } @Override @@ -207,4 +210,9 @@ public class FakeFeatureFactory extends FeatureFactory { public FaceFeatureProvider getFaceFeatureProvider() { return mFaceFeatureProvider; } + + @Override + public BluetoothFeatureProvider getBluetoothFeatureProvider(Context context) { + return mBluetoothFeatureProvider; + } }