diff --git a/res/values/themes.xml b/res/values/themes.xml index 63944d9bb34..dd711943940 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -170,6 +170,7 @@ @*android:color/primary_device_default_settings_light @*android:color/primary_dark_device_default_settings_light @*android:color/accent_device_default_light + @style/PreferenceTheme diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml new file mode 100644 index 00000000000..7aed02158cc --- /dev/null +++ b/res/xml/top_level_settings.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java index 199331d5b21..c85aad76b79 100644 --- a/src/com/android/settings/dashboard/SummaryLoader.java +++ b/src/com/android/settings/dashboard/SummaryLoader.java @@ -42,6 +42,12 @@ import com.android.settingslib.utils.ThreadUtils; import java.lang.reflect.Field; import java.util.List; +/** + * TODO(b/110405144): Remove this when all top level settings are converted to PreferenceControllers + * + * @deprecated + */ +@Deprecated public class SummaryLoader { private static final boolean DEBUG = DashboardSummary.DEBUG; private static final String TAG = "SummaryLoader"; diff --git a/src/com/android/settings/homepage/HomepageFragment.java b/src/com/android/settings/homepage/HomepageFragment.java index ff89dd5a4dc..2e22a035d9b 100644 --- a/src/com/android/settings/homepage/HomepageFragment.java +++ b/src/com/android/settings/homepage/HomepageFragment.java @@ -26,6 +26,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toolbar; +import androidx.annotation.NonNull; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.SettingsHomepageActivity; @@ -39,8 +41,6 @@ import com.google.android.material.bottomappbar.BottomAppBar; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import androidx.annotation.NonNull; - public class HomepageFragment extends InstrumentedFragment { private static final String TAG = "HomepageFragment"; @@ -70,7 +70,7 @@ public class HomepageFragment extends InstrumentedFragment { private void setupBottomBar() { final Activity activity = getActivity(); - mSearchButton = (FloatingActionButton) activity.findViewById(R.id.search_fab); + mSearchButton = activity.findViewById(R.id.search_fab); mSearchButton.setOnClickListener(v -> { final Intent intent = SearchFeatureProvider.SEARCH_UI_INTENT; @@ -79,7 +79,7 @@ public class HomepageFragment extends InstrumentedFragment { startActivityForResult(intent, 0 /* requestCode */); }); mBottomSheetBehavior = BottomSheetBehavior.from(activity.findViewById(R.id.bottom_sheet)); - final BottomAppBar bottomBar = (BottomAppBar) activity.findViewById(R.id.bar); + final BottomAppBar bottomBar = activity.findViewById(R.id.bar); bottomBar.setOnClickListener(v -> { mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); }); @@ -87,7 +87,7 @@ public class HomepageFragment extends InstrumentedFragment { final int screenWidthpx = getResources().getDisplayMetrics().widthPixels; final View searchbar = activity.findViewById(R.id.search_bar_container); final View bottombar = activity.findViewById(R.id.bar); - final Toolbar searchActionBar = (Toolbar) activity.findViewById(R.id.search_action_bar); + final Toolbar searchActionBar = activity.findViewById(R.id.search_action_bar); searchActionBar.setNavigationIcon(R.drawable.ic_search_floating_24dp); @@ -95,6 +95,7 @@ public class HomepageFragment extends InstrumentedFragment { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { if (!mBottomFragmentLoaded) { + // TODO(b/110405144): Switch to {@link TopLevelSettings} when it's ready. SettingsHomepageActivity.switchToFragment(getActivity(), R.id.bottom_sheet_fragment, DashboardSummary.class.getName()); mBottomFragmentLoaded = true; diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java new file mode 100644 index 00000000000..5c682cc12df --- /dev/null +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -0,0 +1,110 @@ +/* + * 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.homepage; + +import static com.android.settings.search.actionbar.SearchMenuController + .NEED_SEARCH_ICON_IN_ACTION_BAR; +import static com.android.settingslib.search.SearchIndexable.MOBILE; + +import android.content.Context; +import android.os.Bundle; +import android.provider.SearchIndexableResource; + +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.core.instrumentation.Instrumentable; +import com.android.settingslib.search.SearchIndexable; + +import java.util.Arrays; +import java.util.List; + +@SearchIndexable(forTarget = MOBILE) +public class TopLevelSettings extends DashboardFragment implements + PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { + + private static final String TAG = "TopLevelSettings"; + + public TopLevelSettings() { + final Bundle args = new Bundle(); + // Disable the search icon because this page uses a full search view in actionbar. + args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false); + setArguments(args); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.top_level_settings; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DASHBOARD_SUMMARY; + } + + @Override + public int getHelpResource() { + // Disable the help icon because this page uses a full search view in actionbar. + return 0; + } + + @Override + public Fragment getCallbackFragment() { + return this; + } + + @Override + public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { + new SubSettingLauncher(getActivity()) + .setDestination(pref.getFragment()) + .setArguments(pref.getExtras()) + .setSourceMetricsCategory(caller instanceof Instrumentable + ? ((Instrumentable) caller).getMetricsCategory() + : Instrumentable.METRICS_CATEGORY_UNKNOWN) + .setTitleRes(-1) + .launch(); + return true; + } + + public static final 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.top_level_settings; + return Arrays.asList(sir); + } + + @Override + protected boolean isPageSearchEnabled(Context context) { + // Never searchable, all entries in this page are already indexed elsewhere. + return false; + } + }; +} diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index d5ef9aa0503..fbb0b20a543 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -149,6 +149,7 @@ public class NetworkDashboardFragment extends DashboardFragment implements return 0; } + // TODO(b/110405144): Remove SummaryProvider @VisibleForTesting static class SummaryProvider implements SummaryLoader.SummaryProvider { diff --git a/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java new file mode 100644 index 00000000000..567e52e596f --- /dev/null +++ b/src/com/android/settings/network/TopLevelNetworkEntryPreferenceController.java @@ -0,0 +1,79 @@ +/* + * 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 android.content.Context; +import android.icu.text.ListFormatter; +import android.text.BidiFormatter; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.WifiMasterSwitchPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class TopLevelNetworkEntryPreferenceController extends BasePreferenceController { + + private final WifiMasterSwitchPreferenceController mWifiPreferenceController; + private final MobileNetworkPreferenceController mMobileNetworkPreferenceController; + private final TetherPreferenceController mTetherPreferenceController; + + public TopLevelNetworkEntryPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mMobileNetworkPreferenceController = new MobileNetworkPreferenceController(mContext); + mTetherPreferenceController = new TetherPreferenceController( + mContext, null /* lifecycle */); + mWifiPreferenceController = new WifiMasterSwitchPreferenceController( + mContext, null /* metrics */); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public CharSequence getSummary() { + final String wifiSummary = BidiFormatter.getInstance() + .unicodeWrap(mContext.getString(R.string.wifi_settings_title)); + final String mobileSummary = mContext.getString( + R.string.network_dashboard_summary_mobile); + final String dataUsageSummary = mContext.getString( + R.string.network_dashboard_summary_data_usage); + final String hotspotSummary = mContext.getString( + R.string.network_dashboard_summary_hotspot); + + final List summaries = new ArrayList<>(); + if (mWifiPreferenceController.isAvailable() + && !TextUtils.isEmpty(wifiSummary)) { + summaries.add(wifiSummary); + } + if (mMobileNetworkPreferenceController.isAvailable() && !TextUtils.isEmpty(mobileSummary)) { + summaries.add(mobileSummary); + } + if (!TextUtils.isEmpty(dataUsageSummary)) { + summaries.add(dataUsageSummary); + } + if (mTetherPreferenceController.isAvailable() + && !TextUtils.isEmpty(hotspotSummary)) { + summaries.add(hotspotSummary); + } + return ListFormatter.getInstance().format(summaries); + } +} diff --git a/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java new file mode 100644 index 00000000000..22aaf2a8aaf --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/TopLevelNetworkEntryPreferenceControllerTest.java @@ -0,0 +1,86 @@ +/* + * 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.Mockito.when; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils; +import com.android.settings.wifi.WifiMasterSwitchPreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedLockUtils.class) +public class TopLevelNetworkEntryPreferenceControllerTest { + + @Mock + private WifiMasterSwitchPreferenceController mWifiPreferenceController; + @Mock + private MobileNetworkPreferenceController mMobileNetworkPreferenceController; + @Mock + private TetherPreferenceController mTetherPreferenceController; + + private Context mContext; + private TopLevelNetworkEntryPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mController = new TopLevelNetworkEntryPreferenceController(mContext, "test_key"); + + ReflectionHelpers.setField(mController, "mWifiPreferenceController", + mWifiPreferenceController); + ReflectionHelpers.setField(mController, "mMobileNetworkPreferenceController", + mMobileNetworkPreferenceController); + ReflectionHelpers.setField(mController, "mTetherPreferenceController", + mTetherPreferenceController); + + } + + @Test + public void getSummary_hasMobileAndHotspot_shouldReturnMobileSummary() { + when(mWifiPreferenceController.isAvailable()).thenReturn(true); + when(mMobileNetworkPreferenceController.isAvailable()).thenReturn(true); + when(mTetherPreferenceController.isAvailable()).thenReturn(true); + + assertThat(mController.getSummary()) + .isEqualTo("Wi\u2011Fi, mobile, data usage, and hotspot"); + } + + @Test + public void getSummary_noMobileOrHotspot_shouldReturnSimpleSummary() { + when(mWifiPreferenceController.isAvailable()).thenReturn(true); + when(mMobileNetworkPreferenceController.isAvailable()).thenReturn(false); + when(mTetherPreferenceController.isAvailable()).thenReturn(false); + + assertThat(mController.getSummary()) + .isEqualTo("Wi\u2011Fi and data usage"); + } +}