diff --git a/res/values/themes.xml b/res/values/themes.xml index 43a9b78f777..cc470cc6d92 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -171,6 +171,7 @@ @*android:color/primary_dark_device_default_settings_light @*android:color/accent_device_default_light @style/PreferenceTheme + true diff --git a/src/com/android/settings/SettingsHomepageActivity.java b/src/com/android/settings/SettingsHomepageActivity.java index 6eef99c6d1b..859e6359158 100644 --- a/src/com/android/settings/SettingsHomepageActivity.java +++ b/src/com/android/settings/SettingsHomepageActivity.java @@ -40,9 +40,12 @@ public class SettingsHomepageActivity extends SettingsBaseActivity { settings.setAction("android.settings.SETTINGS"); startActivity(settings); finish(); + return; } setContentView(R.layout.settings_homepage); - switchToFragment(this, R.id.main_content, HomepageFragment.class.getName()); + if (savedInstanceState == null) { + switchToFragment(this, R.id.main_content, HomepageFragment.class.getName()); + } } public static boolean isDynamicHomepageEnabled(Context context) { diff --git a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java index 2b92ccbc115..8bb8b929f36 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java @@ -236,6 +236,7 @@ public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment { CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT); + break; } } catch (CameraAccessException e) { Log.e(TAG, "Unable to access camera", e); diff --git a/src/com/android/settings/core/FeatureFlags.java b/src/com/android/settings/core/FeatureFlags.java index c6b9d836828..c8ae82f4859 100644 --- a/src/com/android/settings/core/FeatureFlags.java +++ b/src/com/android/settings/core/FeatureFlags.java @@ -25,4 +25,5 @@ public class FeatureFlags { public static final String AUDIO_SWITCHER_SETTINGS = "settings_audio_switcher"; public static final String DYNAMIC_HOMEPAGE = "settings_dynamic_homepage"; public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; + public static final String CONDITION_MANAGER_V2 = "settings_condition_manager_v2"; } diff --git a/src/com/android/settings/dashboard/DashboardAdapter.java b/src/com/android/settings/dashboard/DashboardAdapter.java index a2d810b4e9e..488396f2a7c 100644 --- a/src/com/android/settings/dashboard/DashboardAdapter.java +++ b/src/com/android/settings/dashboard/DashboardAdapter.java @@ -42,6 +42,8 @@ import com.android.settings.dashboard.DashboardData.ConditionHeaderData; import com.android.settings.dashboard.suggestions.SuggestionAdapter; import com.android.settings.homepage.conditional.Condition; import com.android.settings.homepage.conditional.ConditionAdapter; +import com.android.settings.homepage.conditional.v2.ConditionManager; +import com.android.settings.homepage.conditional.v2.ConditionalCard; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.RoundedHomepageIcon; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -71,6 +73,7 @@ public class DashboardAdapter extends RecyclerView.Adapter conditions, SuggestionControllerMixinCompat suggestionControllerMixin, - Lifecycle lifecycle) { + List conditions, ConditionManager conditionManager, + SuggestionControllerMixinCompat suggestionControllerMixin, Lifecycle lifecycle) { DashboardCategory category = null; boolean conditionExpanded = false; @@ -96,6 +99,7 @@ public class DashboardAdapter extends RecyclerView.Adapter conditions) { + final DashboardData prevData = mDashboardData; + Log.d(TAG, "adapter setConditions called"); + mDashboardData = new DashboardData.Builder(prevData) + .setConditionsV2(conditions) + .build(); + notifyDashboardDataChanged(prevData); + } + @Override public void onSuggestionClosed(Suggestion suggestion) { final List list = mDashboardData.getSuggestions(); @@ -286,11 +301,31 @@ public class DashboardAdapter extends RecyclerView.Adapter) mDashboardData.getItemEntityByPosition(position), - mDashboardData.isConditionExpanded()); - adapter.addDismissHandling(holder.data); - holder.data.setAdapter(adapter); + final List conditions = (List) mDashboardData.getItemEntityByPosition(position); + final List conditionsV1; + final List conditionsV2; + if (conditions == null || conditions.isEmpty()) { + conditionsV1 = null; + conditionsV2 = null; + } else if (conditions.get(0) instanceof Condition) { + conditionsV1 = conditions; + conditionsV2 = null; + } else { + conditionsV1 = null; + conditionsV2 = conditions; + } + if (conditionsV2 == null) { + final ConditionAdapter adapter = new ConditionAdapter(mContext, + conditionsV1, mDashboardData.isConditionExpanded()); + adapter.addDismissHandling(holder.data); + holder.data.setAdapter(adapter); + } else { + final com.android.settings.homepage.conditional.v2.ConditionAdapter adapter = + new com.android.settings.homepage.conditional.v2.ConditionAdapter( + mContext, mConditionManager, conditionsV2, + mDashboardData.isConditionExpanded()); + holder.data.setAdapter(adapter); + } holder.data.setLayoutManager(new LinearLayoutManager(mContext)); } diff --git a/src/com/android/settings/dashboard/DashboardData.java b/src/com/android/settings/dashboard/DashboardData.java index 9716ae01075..207cc16a705 100644 --- a/src/com/android/settings/dashboard/DashboardData.java +++ b/src/com/android/settings/dashboard/DashboardData.java @@ -25,6 +25,7 @@ import androidx.recyclerview.widget.DiffUtil; import com.android.settings.R; import com.android.settings.homepage.conditional.Condition; +import com.android.settings.homepage.conditional.v2.ConditionalCard; import com.android.settingslib.drawer.DashboardCategory; import com.android.settingslib.drawer.Tile; @@ -58,12 +59,14 @@ public class DashboardData { private final List mItems; private final DashboardCategory mCategory; private final List mConditions; + private final List mConditionsV2; private final List mSuggestions; private final boolean mConditionExpanded; private DashboardData(Builder builder) { mCategory = builder.mCategory; mConditions = builder.mConditions; + mConditionsV2 = builder.mConditionsV2; mSuggestions = builder.mSuggestions; mConditionExpanded = builder.mConditionExpanded; mItems = new ArrayList<>(); @@ -182,8 +185,11 @@ public class DashboardData { * and mIsShowingAll, mConditionExpanded flag. */ private void buildItemsData() { - final List conditions = getConditionsToShow(mConditions); - final boolean hasConditions = sizeOf(conditions) > 0; + final List conditionsV1 = getConditionsToShow(mConditions); + final boolean hasConditionsV1 = sizeOf(conditionsV1) > 0; + final List conditionsV2 = mConditionsV2; + final boolean hasConditionsV2 = sizeOf(conditionsV2) > 0; + final boolean hasConditions = hasConditionsV1 || hasConditionsV2; final List suggestions = getSuggestionsToShow(mSuggestions); final boolean hasSuggestions = sizeOf(suggestions) > 0; @@ -191,25 +197,31 @@ public class DashboardData { /* Suggestion container. This is the card view that contains the list of suggestions. * This will be added whenever the suggestion list is not empty */ addToItemList(suggestions, R.layout.suggestion_container, - STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions); + STABLE_ID_SUGGESTION_CONTAINER, hasSuggestions); /* Divider between suggestion and conditions if both are present. */ addToItemList(null /* item */, R.layout.horizontal_divider, - STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions); + STABLE_ID_SUGGESTION_CONDITION_DIVIDER, hasSuggestions && hasConditions); /* Condition header. This will be present when there is condition and it is collapsed */ - addToItemList(new ConditionHeaderData(conditions), - R.layout.condition_header, - STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded); + addToItemList(new ConditionHeaderData(conditionsV1, conditionsV2), + R.layout.condition_header, + STABLE_ID_CONDITION_HEADER, hasConditions && !mConditionExpanded); /* Condition container. This is the card view that contains the list of conditions. * This will be added whenever the condition list is not empty and expanded */ - addToItemList(conditions, R.layout.condition_container, - STABLE_ID_CONDITION_CONTAINER, hasConditions && mConditionExpanded); + if (hasConditionsV1) { + addToItemList(conditionsV1, R.layout.condition_container, + STABLE_ID_CONDITION_CONTAINER, hasConditionsV1 && mConditionExpanded); + } + if (hasConditionsV2) { + addToItemList(conditionsV2, R.layout.condition_container, + STABLE_ID_CONDITION_CONTAINER, hasConditionsV2 && mConditionExpanded); + } /* Condition footer. This will be present when there is condition and it is expanded */ addToItemList(null /* item */, R.layout.condition_footer, - STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded); + STABLE_ID_CONDITION_FOOTER, hasConditions && mConditionExpanded); if (mCategory != null) { final List tiles = mCategory.getTiles(); @@ -260,6 +272,7 @@ public class DashboardData { public static class Builder { private DashboardCategory mCategory; private List mConditions; + private List mConditionsV2; private List mSuggestions; private boolean mConditionExpanded; @@ -269,6 +282,7 @@ public class DashboardData { public Builder(DashboardData dashboardData) { mCategory = dashboardData.mCategory; mConditions = dashboardData.mConditions; + mConditionsV2 = dashboardData.mConditionsV2; mSuggestions = dashboardData.mSuggestions; mConditionExpanded = dashboardData.mConditionExpanded; } @@ -283,6 +297,11 @@ public class DashboardData { return this; } + public Builder setConditionsV2(List conditions) { + this.mConditionsV2 = conditions; + return this; + } + public Builder setSuggestions(List suggestions) { this.mSuggestions = suggestions; return this; @@ -340,17 +359,17 @@ public class DashboardData { // valid types in field type private static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile; private static final int TYPE_SUGGESTION_CONTAINER = - R.layout.suggestion_container; + R.layout.suggestion_container; private static final int TYPE_CONDITION_CONTAINER = - R.layout.condition_container; + R.layout.condition_container; private static final int TYPE_CONDITION_HEADER = - R.layout.condition_header; + R.layout.condition_header; private static final int TYPE_CONDITION_FOOTER = - R.layout.condition_footer; + R.layout.condition_footer; private static final int TYPE_SUGGESTION_CONDITION_DIVIDER = R.layout.horizontal_divider; @IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONTAINER, TYPE_CONDITION_CONTAINER, - TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER}) + TYPE_CONDITION_HEADER, TYPE_CONDITION_FOOTER, TYPE_SUGGESTION_CONDITION_DIVIDER}) @Retention(RetentionPolicy.SOURCE) public @interface ItemTypes { } @@ -408,7 +427,7 @@ public class DashboardData { // Only check title and summary for dashboard tile return TextUtils.equals(localTile.title, targetTile.title) - && TextUtils.equals(localTile.summary, targetTile.summary); + && TextUtils.equals(localTile.summary, targetTile.summary); case TYPE_SUGGESTION_CONTAINER: case TYPE_CONDITION_CONTAINER: // Fall through to default @@ -428,13 +447,22 @@ public class DashboardData { public final CharSequence title; public final int conditionCount; - public ConditionHeaderData(List conditions) { - conditionCount = sizeOf(conditions); - title = conditionCount > 0 ? conditions.get(0).getTitle() : null; - conditionIcons = new ArrayList<>(); - for (int i = 0; conditions != null && i < conditions.size(); i++) { - final Condition condition = conditions.get(i); - conditionIcons.add(condition.getIcon()); + public ConditionHeaderData(List conditions, List conditionsV2) { + if (conditionsV2 == null) { + conditionCount = sizeOf(conditions); + title = conditionCount > 0 ? conditions.get(0).getTitle() : null; + conditionIcons = new ArrayList<>(); + for (int i = 0; conditions != null && i < conditions.size(); i++) { + final Condition condition = conditions.get(i); + conditionIcons.add(condition.getIcon()); + } + } else { + conditionCount = sizeOf(conditionsV2); + title = conditionCount > 0 ? conditionsV2.get(0).getTitle() : null; + conditionIcons = new ArrayList<>(); + for (ConditionalCard card : conditionsV2) { + conditionIcons.add(card.getIcon()); + } } } } diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java index d7595bf8634..a37ab5a246f 100644 --- a/src/com/android/settings/dashboard/DashboardSummary.java +++ b/src/com/android/settings/dashboard/DashboardSummary.java @@ -38,8 +38,8 @@ import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.SettingsBaseActivity.CategoryListener; import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; import com.android.settings.homepage.conditional.Condition; -import com.android.settings.homepage.conditional.ConditionManager; import com.android.settings.homepage.conditional.ConditionListener; +import com.android.settings.homepage.conditional.ConditionManager; import com.android.settings.homepage.conditional.FocusRecyclerView; import com.android.settings.homepage.conditional.FocusRecyclerView.FocusListener; import com.android.settings.overlay.FeatureFactory; @@ -74,6 +74,7 @@ public class DashboardSummary extends InstrumentedFragment private DashboardAdapter mAdapter; private SummaryLoader mSummaryLoader; private ConditionManager mConditionManager; + private com.android.settings.homepage.conditional.v2.ConditionManager mConditionManager2; private LinearLayoutManager mLayoutManager; private SuggestionControllerMixinCompat mSuggestionControllerMixin; private DashboardFeatureProvider mDashboardFeatureProvider; @@ -123,7 +124,18 @@ public class DashboardSummary extends InstrumentedFragment mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE); mConditionManager = ConditionManager.get(activity, false); - getSettingsLifecycle().addObserver(mConditionManager); + if (com.android.settings.homepage.conditional.v2.ConditionManager.isEnabled(activity)) { + mConditionManager = null; + mConditionManager2 = + new com.android.settings.homepage.conditional.v2.ConditionManager( + activity, this /* listener */); + } else { + mConditionManager = ConditionManager.get(activity, false); + mConditionManager2 = null; + } + if (mConditionManager2 == null) { + getSettingsLifecycle().addObserver(mConditionManager); + } if (savedInstanceState != null) { mIsOnCategoriesChangedCalled = savedInstanceState.getBoolean(STATE_CATEGORIES_CHANGE_CALLED); @@ -147,11 +159,15 @@ public class DashboardSummary extends InstrumentedFragment ((SettingsBaseActivity) getActivity()).addCategoryListener(this); mSummaryLoader.setListening(true); final int metricsCategory = getMetricsCategory(); - for (Condition c : mConditionManager.getConditions()) { - if (c.shouldShow()) { - mMetricsFeatureProvider.visible(getContext(), metricsCategory, - c.getMetricsConstant()); + if (mConditionManager2 == null) { + for (Condition c : mConditionManager.getConditions()) { + if (c.shouldShow()) { + mMetricsFeatureProvider.visible(getContext(), metricsCategory, + c.getMetricsConstant()); + } } + } else { + mConditionManager2.startMonitoringStateChange(); } if (DEBUG_TIMING) { Log.d(TAG, "onResume took " + (System.currentTimeMillis() - startTime) + " ms"); @@ -164,24 +180,42 @@ public class DashboardSummary extends InstrumentedFragment ((SettingsBaseActivity) getActivity()).remCategoryListener(this); mSummaryLoader.setListening(false); - for (Condition c : mConditionManager.getConditions()) { - if (c.shouldShow()) { - mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant()); + if (mConditionManager2 == null) { + for (Condition c : mConditionManager.getConditions()) { + if (c.shouldShow()) { + mMetricsFeatureProvider.hidden(getContext(), c.getMetricsConstant()); + } } } + // Unregister condition listeners. + if (mConditionManager != null) { + mConditionManager.remListener(this); + } + if (mConditionManager2 != null) { + mConditionManager2.stopMonitoringStateChange(); + } } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { long startTime = System.currentTimeMillis(); - if (hasWindowFocus) { - Log.d(TAG, "Listening for condition changes"); - mConditionManager.addListener(this); - Log.d(TAG, "conditions refreshed"); - mConditionManager.refreshAll(); + if (mConditionManager2 == null) { + if (hasWindowFocus) { + Log.d(TAG, "Listening for condition changes"); + mConditionManager.addListener(this); + Log.d(TAG, "conditions refreshed"); + mConditionManager.refreshAll(); + } else { + Log.d(TAG, "Stopped listening for condition changes"); + mConditionManager.remListener(this); + } } else { - Log.d(TAG, "Stopped listening for condition changes"); - mConditionManager.remListener(this); + // TODO(b/112485407): Register monitoring for condition manager v2. + if (hasWindowFocus) { + mConditionManager2.startMonitoringStateChange(); + } else { + mConditionManager2.stopMonitoringStateChange(); + } } if (DEBUG_TIMING) { Log.d(TAG, "onWindowFocusChanged took " @@ -215,7 +249,9 @@ public class DashboardSummary extends InstrumentedFragment mDashboard.setListener(this); mDashboard.setItemAnimator(new DashboardItemAnimator()); mAdapter = new DashboardAdapter(getContext(), bundle, - mConditionManager.getConditions(), mSuggestionControllerMixin, + mConditionManager == null ? null : mConditionManager.getConditions(), + mConditionManager2, + mSuggestionControllerMixin, getSettingsLifecycle()); mDashboard.setAdapter(mAdapter); mSummaryLoader.setSummaryConsumer(mAdapter); @@ -255,10 +291,15 @@ public class DashboardSummary extends InstrumentedFragment // constructor when we create the view, the first handling is not necessary. // But, on the subsequent calls we need to handle it because there might be real changes to // conditions. - if (mOnConditionsChangedCalled) { + if (mOnConditionsChangedCalled || mConditionManager2 != null) { final boolean scrollToTop = mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1; - mAdapter.setConditions(mConditionManager.getConditions()); + if (mConditionManager2 == null) { + mAdapter.setConditions(mConditionManager.getConditions()); + } else { + mAdapter.setConditionsV2(mConditionManager2.getDisplayableCards()); + } + if (scrollToTop) { mDashboard.scrollToPosition(0); } diff --git a/src/com/android/settings/homepage/HomepageFragment.java b/src/com/android/settings/homepage/HomepageFragment.java index 2e22a035d9b..9ed4e6a5e0b 100644 --- a/src/com/android/settings/homepage/HomepageFragment.java +++ b/src/com/android/settings/homepage/HomepageFragment.java @@ -16,7 +16,6 @@ package com.android.settings.homepage; - import android.app.ActionBar; import android.app.Activity; import android.content.Intent; @@ -37,17 +36,20 @@ import com.android.settings.dashboard.DashboardSummary; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.SearchFeatureProvider; -import com.google.android.material.bottomappbar.BottomAppBar; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.floatingactionbutton.FloatingActionButton; public class HomepageFragment extends InstrumentedFragment { private static final String TAG = "HomepageFragment"; + private static final String SAVE_BOTTOMBAR_STATE = "bottombar_state"; + private static final String SAVE_BOTTOM_FRAGMENT_LOADED = "bottom_fragment_loaded"; private FloatingActionButton mSearchButton; private BottomSheetBehavior mBottomSheetBehavior; - private boolean mBottomFragmentLoaded = false; + private View mBottomBar; + private View mSearchBar; + private boolean mBottomFragmentLoaded; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -61,6 +63,22 @@ public class HomepageFragment extends InstrumentedFragment { super.onActivityCreated(savedInstanceState); setupBottomBar(); setupSearchBar(); + if (savedInstanceState != null) { + final int bottombarState = savedInstanceState.getInt(SAVE_BOTTOMBAR_STATE); + mBottomFragmentLoaded = savedInstanceState.getBoolean(SAVE_BOTTOM_FRAGMENT_LOADED); + mBottomSheetBehavior.setState(bottombarState); + setBarState(bottombarState); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + if (mBottomSheetBehavior != null) { + outState.putInt(SAVE_BOTTOMBAR_STATE, mBottomSheetBehavior.getState()); + outState.putBoolean(SAVE_BOTTOM_FRAGMENT_LOADED, mBottomFragmentLoaded); + } } @Override @@ -70,8 +88,8 @@ public class HomepageFragment extends InstrumentedFragment { private void setupBottomBar() { final Activity activity = getActivity(); - mSearchButton = activity.findViewById(R.id.search_fab); + mSearchButton = activity.findViewById(R.id.search_fab); mSearchButton.setOnClickListener(v -> { final Intent intent = SearchFeatureProvider.SEARCH_UI_INTENT; intent.setPackage(FeatureFactory.getFactory(activity) @@ -79,18 +97,16 @@ public class HomepageFragment extends InstrumentedFragment { startActivityForResult(intent, 0 /* requestCode */); }); mBottomSheetBehavior = BottomSheetBehavior.from(activity.findViewById(R.id.bottom_sheet)); - final BottomAppBar bottomBar = activity.findViewById(R.id.bar); - bottomBar.setOnClickListener(v -> { + mSearchBar = activity.findViewById(R.id.search_bar_container); + mBottomBar = activity.findViewById(R.id.bar); + mBottomBar.setOnClickListener(v -> { mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); }); 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 = activity.findViewById(R.id.search_action_bar); searchActionBar.setNavigationIcon(R.drawable.ic_search_floating_24dp); - mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { @@ -100,31 +116,35 @@ public class HomepageFragment extends InstrumentedFragment { R.id.bottom_sheet_fragment, DashboardSummary.class.getName()); mBottomFragmentLoaded = true; } - if (newState == BottomSheetBehavior.STATE_EXPANDED) { - bottombar.setVisibility(View.INVISIBLE); - searchbar.setVisibility(View.VISIBLE); - mSearchButton.setVisibility(View.GONE); - } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) { - bottombar.setVisibility(View.VISIBLE); - searchbar.setVisibility(View.INVISIBLE); - mSearchButton.setVisibility(View.VISIBLE); - } else if (newState == BottomSheetBehavior.STATE_SETTLING) { - bottombar.setVisibility(View.VISIBLE); - searchbar.setVisibility(View.VISIBLE); - mSearchButton.setVisibility(View.VISIBLE); - } + setBarState(newState); } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { - bottombar.setAlpha(1 - slideOffset); + mBottomBar.setAlpha(1 - slideOffset); mSearchButton.setAlpha(1 - slideOffset); - searchbar.setAlpha(slideOffset); - searchbar.setPadding((int) (screenWidthpx * (1 - slideOffset)), 0, 0, 0); + mSearchBar.setAlpha(slideOffset); + mSearchBar.setPadding((int) (screenWidthpx * (1 - slideOffset)), 0, 0, 0); } }); } + private void setBarState(int bottomSheetState) { + if (bottomSheetState == BottomSheetBehavior.STATE_EXPANDED) { + mBottomBar.setVisibility(View.INVISIBLE); + mSearchBar.setVisibility(View.VISIBLE); + mSearchButton.setVisibility(View.GONE); + } else if (bottomSheetState == BottomSheetBehavior.STATE_COLLAPSED) { + mBottomBar.setVisibility(View.VISIBLE); + mSearchBar.setVisibility(View.INVISIBLE); + mSearchButton.setVisibility(View.VISIBLE); + } else if (bottomSheetState == BottomSheetBehavior.STATE_SETTLING) { + mBottomBar.setVisibility(View.VISIBLE); + mSearchBar.setVisibility(View.VISIBLE); + mSearchButton.setVisibility(View.VISIBLE); + } + } + //TODO(110767984), copied from settingsActivity. We have to merge them private void setupSearchBar() { final Activity activity = getActivity(); diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java b/src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java new file mode 100644 index 00000000000..51d8c47e18c --- /dev/null +++ b/src/com/android/settings/homepage/conditional/v2/ConditionAdapter.java @@ -0,0 +1,130 @@ +/* + * 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.conditional.v2; + +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardAdapter; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import java.util.List; + +public class ConditionAdapter extends RecyclerView.Adapter { + + private final Context mContext; + private final MetricsFeatureProvider mMetricsFeatureProvider; + private final ConditionManager mConditionManager; + private final List mConditions; + private final boolean mExpanded; + + public ConditionAdapter(Context context, ConditionManager conditionManager, + List conditions, boolean expanded) { + mContext = context; + mConditionManager = conditionManager; + mConditions = conditions; + mExpanded = expanded; + mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + + setHasStableIds(true); + } + + @Override + public DashboardAdapter.DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new DashboardAdapter.DashboardItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(viewType, parent, false)); + } + + @Override + public void onBindViewHolder(DashboardAdapter.DashboardItemHolder holder, int position) { + final ConditionalCard condition = mConditions.get(position); + final boolean isLastItem = position == mConditions.size() - 1; + bindViews(condition, holder, isLastItem); + } + + @Override + public long getItemId(int position) { + return mConditions.get(position).getId(); + } + + @Override + public int getItemViewType(int position) { + return R.layout.condition_tile; + } + + @Override + public int getItemCount() { + if (mExpanded) { + return mConditions.size(); + } + return 0; + } + + private void bindViews(final ConditionalCard condition, + DashboardAdapter.DashboardItemHolder view, boolean isLastItem) { + mMetricsFeatureProvider.visible(mContext, MetricsProto.MetricsEvent.DASHBOARD_SUMMARY, + condition.getMetricsConstant()); + view.itemView.findViewById(R.id.content).setOnClickListener( + v -> { + mMetricsFeatureProvider.action(mContext, + MetricsProto.MetricsEvent.ACTION_SETTINGS_CONDITION_CLICK, + condition.getMetricsConstant()); + mConditionManager.onPrimaryClick(mContext, condition.getId()); + }); + view.icon.setImageDrawable(condition.getIcon()); + view.title.setText(condition.getTitle()); + view.summary.setText(condition.getSummary()); + + setViewVisibility(view.itemView, R.id.divider, !isLastItem); + + final CharSequence action = condition.getActionText(); + final boolean hasButtons = !TextUtils.isEmpty(action); + setViewVisibility(view.itemView, R.id.buttonBar, hasButtons); + + final Button button = view.itemView.findViewById(R.id.first_action); + if (hasButtons) { + button.setVisibility(View.VISIBLE); + button.setText(action); + button.setOnClickListener(v -> { + final Context context = v.getContext(); + mMetricsFeatureProvider.action( + context, MetricsProto.MetricsEvent.ACTION_SETTINGS_CONDITION_BUTTON, + condition.getMetricsConstant()); + mConditionManager.onActionClick(condition.getId()); + }); + } else { + button.setVisibility(View.GONE); + } + + } + + private void setViewVisibility(View containerView, int viewId, boolean visible) { + View view = containerView.findViewById(viewId); + if (view != null) { + view.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } +} diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionManager.java b/src/com/android/settings/homepage/conditional/v2/ConditionManager.java new file mode 100644 index 00000000000..e6530d31ca2 --- /dev/null +++ b/src/com/android/settings/homepage/conditional/v2/ConditionManager.java @@ -0,0 +1,149 @@ +/* + * 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.conditional.v2; + +import android.content.Context; +import android.util.FeatureFlagUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.settings.core.FeatureFlags; +import com.android.settings.homepage.conditional.ConditionListener; + +import java.util.ArrayList; +import java.util.List; + +public class ConditionManager { + private static final String TAG = "ConditionManager"; + + @VisibleForTesting + final List mCandidates; + @VisibleForTesting + final List mCardControllers; + + private final Context mAppContext; + private final ConditionListener mListener; + + private boolean mIsListeningToStateChange; + + /** + * Whether or not the new condition manager is should be used. + */ + public static boolean isEnabled(Context context) { + return FeatureFlagUtils.isEnabled(context, FeatureFlags.CONDITION_MANAGER_V2); + } + + public ConditionManager(Context context, ConditionListener listener) { + mAppContext = context.getApplicationContext(); + mCandidates = new ArrayList<>(); + mCardControllers = new ArrayList<>(); + mListener = listener; + initCandidates(); + } + + /** + * Returns a list of {@link ConditionalCard}s eligible for display. + */ + public List getDisplayableCards() { + final List cards = new ArrayList<>(); + for (ConditionalCard card : mCandidates) { + if (getController(card.getId()).isDisplayable()) { + cards.add(card); + } + } + return cards; + } + + /** + * Handler when the card is clicked. + * + * @see {@link ConditionalCardController#onPrimaryClick(Context)} + */ + public void onPrimaryClick(Context context, long id) { + getController(id).onPrimaryClick(context); + } + + /** + * Handler when the card action is clicked. + * + * @see {@link ConditionalCardController#onActionClick()} + */ + public void onActionClick(long id) { + getController(id).onActionClick(); + onConditionChanged(); + } + + + /** + * Start monitoring state change for all conditions + */ + public void startMonitoringStateChange() { + if (mIsListeningToStateChange) { + Log.d(TAG, "Already listening to condition state changes, skipping"); + return; + } + mIsListeningToStateChange = true; + for (ConditionalCardController controller : mCardControllers) { + controller.startMonitoringStateChange(); + } + // Force a refresh on listener + onConditionChanged(); + } + + /** + * Stop monitoring state change for all conditions + */ + public void stopMonitoringStateChange() { + if (!mIsListeningToStateChange) { + Log.d(TAG, "Not listening to condition state changes, skipping"); + return; + } + for (ConditionalCardController controller : mCardControllers) { + controller.stopMonitoringStateChange(); + } + mIsListeningToStateChange = false; + } + + /** + * Called when some conditional card's state has changed + */ + void onConditionChanged() { + if (mListener != null) { + mListener.onConditionsChanged(); + } + } + + @NonNull + T getController(long id) { + for (ConditionalCardController controller : mCardControllers) { + if (controller.getId() == id) { + return (T) controller; + } + } + throw new IllegalStateException("Cannot find controller for " + id); + } + + private void initCandidates() { + // Initialize controllers first. + mCardControllers.add(new DndConditionCardController(mAppContext, this /* manager */)); + + // Initialize ui model later. UI model depends on controller. + mCandidates.add(new DndConditionCard(mAppContext, this /* manager */)); + } +} diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionalCard.java b/src/com/android/settings/homepage/conditional/v2/ConditionalCard.java new file mode 100644 index 00000000000..da832b7db86 --- /dev/null +++ b/src/com/android/settings/homepage/conditional/v2/ConditionalCard.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.homepage.conditional.v2; + +import android.graphics.drawable.Drawable; + +/** + * UI Model for a conditional card displayed on homepage. + */ +public interface ConditionalCard { + + /** + * A stable ID for this card. + * + * @see {@link ConditionalCardController#getId()} + */ + long getId(); + + /** + * The text display on the card for click action. + */ + CharSequence getActionText(); + + /** + * Metrics constant used for logging user interaction. + */ + int getMetricsConstant(); + + Drawable getIcon(); + + CharSequence getTitle(); + + CharSequence getSummary(); +} diff --git a/src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java b/src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java new file mode 100644 index 00000000000..7919d7356b4 --- /dev/null +++ b/src/com/android/settings/homepage/conditional/v2/ConditionalCardController.java @@ -0,0 +1,51 @@ +/* + * 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.conditional.v2; + +import android.content.Context; + +/** + * Data controller for a {@link ConditionalCard}. + */ +public interface ConditionalCardController { + + /** + * A stable ID for this card. + * + * @see {@link ConditionalCard#getId()} + */ + long getId(); + + /** + * Whether or not the card is displayable on the ui. + */ + boolean isDisplayable(); + + /** + * Handler when the card is clicked. + */ + void onPrimaryClick(Context context); + + /** + * Handler when the card action is clicked. + */ + void onActionClick(); + + void startMonitoringStateChange(); + + void stopMonitoringStateChange(); +} diff --git a/src/com/android/settings/homepage/conditional/v2/DndConditionCard.java b/src/com/android/settings/homepage/conditional/v2/DndConditionCard.java new file mode 100644 index 00000000000..8e0fdd59aaf --- /dev/null +++ b/src/com/android/settings/homepage/conditional/v2/DndConditionCard.java @@ -0,0 +1,66 @@ +/* + * 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.conditional.v2; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; + +public class DndConditionCard implements ConditionalCard { + + private final Context mAppContext; + private final ConditionManager mManager; + private final DndConditionCardController mController; + + public DndConditionCard(Context appContext, ConditionManager manager) { + mAppContext = appContext; + mManager = manager; + mController = manager.getController(getId()); + } + + @Override + public long getId() { + return DndConditionCardController.ID; + } + + @Override + public Drawable getIcon() { + return mAppContext.getDrawable(R.drawable.ic_do_not_disturb_on_24dp); + } + + @Override + public CharSequence getTitle() { + return mAppContext.getText(R.string.condition_zen_title); + } + + @Override + public CharSequence getSummary() { + return mController.getSummary(); + } + + @Override + public CharSequence getActionText() { + return mAppContext.getText(R.string.condition_turn_off); + } + + @Override + public int getMetricsConstant() { + return MetricsProto.MetricsEvent.SETTINGS_CONDITION_DND; + } +} diff --git a/src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java b/src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java new file mode 100644 index 00000000000..9e302571c53 --- /dev/null +++ b/src/com/android/settings/homepage/conditional/v2/DndConditionCardController.java @@ -0,0 +1,113 @@ +/* + * 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.conditional.v2; + +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.provider.Settings; +import android.service.notification.ZenModeConfig; + +import androidx.annotation.VisibleForTesting; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.notification.ZenModeSettings; + +import java.util.Objects; + + +public class DndConditionCardController implements ConditionalCardController { + static final int ID = Objects.hash("DndConditionCardController"); + + @VisibleForTesting + static final IntentFilter DND_FILTER = + new IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL); + + private static final String TAG = "DndCondition"; + private final Context mAppContext; + private final ConditionManager mConditionManager; + private final NotificationManager mNotificationManager; + private final Receiver mReceiver; + + public DndConditionCardController(Context appContext, ConditionManager conditionManager) { + mAppContext = appContext; + mConditionManager = conditionManager; + mNotificationManager = mAppContext.getSystemService(NotificationManager.class); + mReceiver = new Receiver(); + } + + @Override + public long getId() { + return ID; + } + + @Override + public boolean isDisplayable() { + return mNotificationManager.getZenMode() != Settings.Global.ZEN_MODE_OFF; + } + + @Override + public void startMonitoringStateChange() { + mAppContext.registerReceiver(mReceiver, DND_FILTER); + } + + @Override + public void stopMonitoringStateChange() { + mAppContext.unregisterReceiver(mReceiver); + } + + @Override + public void onPrimaryClick(Context context) { + new SubSettingLauncher(context) + .setDestination(ZenModeSettings.class.getName()) + .setSourceMetricsCategory(MetricsProto.MetricsEvent.DASHBOARD_SUMMARY) + .setTitleRes(R.string.zen_mode_settings_title) + .launch(); + } + + @Override + public void onActionClick() { + mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG); + } + + public CharSequence getSummary() { + final int zen = mNotificationManager.getZenMode(); + final ZenModeConfig config; + boolean zenModeEnabled = zen != Settings.Global.ZEN_MODE_OFF; + if (zenModeEnabled) { + config = mNotificationManager.getZenModeConfig(); + } else { + config = null; + } + return ZenModeConfig.getDescription(mAppContext, zen != Settings.Global.ZEN_MODE_OFF, + config, true); + } + + public class Receiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL + .equals(intent.getAction())) { + mConditionManager.onConditionChanged(); + } + } + } +} diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 28e10b2da6c..10111aa7204 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -47,6 +47,10 @@ import android.view.MenuItem; import android.view.View; import android.widget.Toast; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.LinkifyUtils; import com.android.settings.R; @@ -72,10 +76,6 @@ import com.android.settingslib.wifi.WifiTrackerFactory; import java.util.ArrayList; import java.util.List; -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; - /** * Two types of UI are provided here. * @@ -252,60 +252,63 @@ public class WifiSettings extends RestrictedSettingsFragment } mConnectListener = new WifiManager.ActionListener() { - @Override - public void onSuccess() { - } - @Override - public void onFailure(int reason) { - Activity activity = getActivity(); - if (activity != null) { - Toast.makeText(activity, - R.string.wifi_failed_connect_message, - Toast.LENGTH_SHORT).show(); - } - } - }; + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } + } + }; mSaveListener = new WifiManager.ActionListener() { - @Override - public void onSuccess() { - } - @Override - public void onFailure(int reason) { - Activity activity = getActivity(); - if (activity != null) { - Toast.makeText(activity, - R.string.wifi_failed_save_message, - Toast.LENGTH_SHORT).show(); - } - } - }; + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } + } + }; mForgetListener = new WifiManager.ActionListener() { - @Override - public void onSuccess() { - } - @Override - public void onFailure(int reason) { - Activity activity = getActivity(); - if (activity != null) { - Toast.makeText(activity, - R.string.wifi_failed_forget_message, - Toast.LENGTH_SHORT).show(); - } - } - }; + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_forget_message, + Toast.LENGTH_SHORT).show(); + } + } + }; if (savedInstanceState != null) { mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE); if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { mAccessPointSavedState = - savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); + savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); } if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) { mWifiNfcDialogSavedState = - savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE); + savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE); } } @@ -371,7 +374,7 @@ public class WifiSettings extends RestrictedSettingsFragment private WifiEnabler createWifiEnabler() { final SettingsActivity activity = (SettingsActivity) getActivity(); return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()), - mMetricsFeatureProvider); + mMetricsFeatureProvider); } @Override @@ -449,38 +452,38 @@ public class WifiSettings extends RestrictedSettingsFragment @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { - Preference preference = (Preference) view.getTag(); + Preference preference = (Preference) view.getTag(); - if (preference instanceof LongPressAccessPointPreference) { - mSelectedAccessPoint = - ((LongPressAccessPointPreference) preference).getAccessPoint(); - menu.setHeaderTitle(mSelectedAccessPoint.getSsid()); - if (mSelectedAccessPoint.isConnectable()) { - menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect); - } + if (preference instanceof LongPressAccessPointPreference) { + mSelectedAccessPoint = + ((LongPressAccessPointPreference) preference).getAccessPoint(); + menu.setHeaderTitle(mSelectedAccessPoint.getSsid()); + if (mSelectedAccessPoint.isConnectable()) { + menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect); + } - WifiConfiguration config = mSelectedAccessPoint.getConfig(); - // Some configs are ineditable - if (WifiUtils.isNetworkLockedDown(getActivity(), config)) { - return; - } + WifiConfiguration config = mSelectedAccessPoint.getConfig(); + // Some configs are ineditable + if (WifiUtils.isNetworkLockedDown(getActivity(), config)) { + return; + } - if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) { - // Allow forgetting a network if either the network is saved or ephemerally - // connected. (In the latter case, "forget" blacklists the network so it won't - // be used again, ephemerally). - menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget); - } - if (mSelectedAccessPoint.isSaved()) { - menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify); - NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); - if (nfcAdapter != null && nfcAdapter.isEnabled() && - mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) { - // Only allow writing of NFC tags for password-protected networks. - menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc); - } + if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) { + // Allow forgetting a network if either the network is saved or ephemerally + // connected. (In the latter case, "forget" blacklists the network so it won't + // be used again, ephemerally). + menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget); + } + if (mSelectedAccessPoint.isSaved()) { + menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify); + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + if (nfcAdapter != null && nfcAdapter.isEnabled() && + mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) { + // Only allow writing of NFC tags for password-protected networks. + menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc); } } + } } @Override @@ -954,11 +957,11 @@ public class WifiSettings extends RestrictedSettingsFragment PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); ContentResolver contentResolver = getContentResolver(); return Settings.Global.getInt(contentResolver, - Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1 + Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1 && Settings.Global.getInt(contentResolver, - Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1 + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1 && Settings.Global.getInt(contentResolver, - Settings.Global.AIRPLANE_MODE_ON, 0) == 0 + Settings.Global.AIRPLANE_MODE_ON, 0) == 0 && !powerManager.isPowerSaveMode(); } @@ -1119,25 +1122,26 @@ public class WifiSettings extends RestrictedSettingsFragment } public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getRawDataToIndex(Context context, boolean enabled) { - final List result = new ArrayList<>(); - final Resources res = context.getResources(); + new BaseSearchIndexProvider() { + @Override + public List getRawDataToIndex(Context context, + boolean enabled) { + final List result = new ArrayList<>(); + final Resources res = context.getResources(); - // Add fragment title if we are showing this fragment - if (res.getBoolean(R.bool.config_show_wifi_settings)) { - SearchIndexableRaw data = new SearchIndexableRaw(context); - data.title = res.getString(R.string.wifi_settings); - data.screenTitle = res.getString(R.string.wifi_settings); - data.keywords = res.getString(R.string.keywords_wifi); - data.key = DATA_KEY_REFERENCE; - result.add(data); + // Add fragment title if we are showing this fragment + if (res.getBoolean(R.bool.config_show_wifi_settings)) { + SearchIndexableRaw data = new SearchIndexableRaw(context); + data.title = res.getString(R.string.wifi_settings); + data.screenTitle = res.getString(R.string.wifi_settings); + data.keywords = res.getString(R.string.keywords_wifi); + data.key = DATA_KEY_REFERENCE; + result.add(data); + } + + return result; } - - return result; - } - }; + }; private static class SummaryProvider implements SummaryLoader.SummaryProvider, OnSummaryChangeListener { @@ -1170,7 +1174,7 @@ public class WifiSettings extends RestrictedSettingsFragment = new SummaryLoader.SummaryProviderFactory() { @Override public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, - SummaryLoader summaryLoader) { + SummaryLoader summaryLoader) { return new SummaryProvider(activity, summaryLoader); } }; diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java index d3288b6ce7a..a73f4a8700e 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardAdapterTest.java @@ -104,7 +104,8 @@ public class DashboardAdapterTest { mConditionList.add(mCondition); when(mCondition.shouldShow()).thenReturn(true); mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */, - mConditionList, null /* suggestionControllerMixin */, null /* lifecycle */); + mConditionList, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */); when(mView.getTag()).thenReturn(mCondition); } @@ -112,7 +113,8 @@ public class DashboardAdapterTest { public void onSuggestionClosed_notOnlySuggestion_updateSuggestionOnly() { final DashboardAdapter adapter = spy(new DashboardAdapter(mContext, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */)); final List suggestions = makeSuggestions("pkg1", "pkg2", "pkg3"); adapter.setSuggestions(suggestions); @@ -144,8 +146,8 @@ public class DashboardAdapterTest { public void onSuggestionClosed_onlySuggestion_updateDashboardData() { final DashboardAdapter adapter = spy(new DashboardAdapter(mContext, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, - null /* lifecycle */)); + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */)); final List suggestions = makeSuggestions("pkg1"); adapter.setSuggestions(suggestions); final DashboardData dashboardData = adapter.mDashboardData; @@ -161,8 +163,8 @@ public class DashboardAdapterTest { public void onSuggestionClosed_notInSuggestionList_shouldNotUpdateSuggestionList() { final DashboardAdapter adapter = spy(new DashboardAdapter(mContext, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, - null /* lifecycle */)); + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */)); final List suggestions = makeSuggestions("pkg1"); adapter.setSuggestions(suggestions); @@ -176,7 +178,8 @@ public class DashboardAdapterTest { @Test public void onBindSuggestion_shouldSetSuggestionAdapterAndNoCrash() { mDashboardAdapter = new DashboardAdapter(mContext, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */); final List suggestions = makeSuggestions("pkg1"); mDashboardAdapter.setSuggestions(suggestions); @@ -212,7 +215,8 @@ public class DashboardAdapterTest { .thenReturn(context.getDrawable(R.drawable.ic_settings)); mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */); ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); mDashboardAdapter.onBindTile(holder, tile); @@ -232,7 +236,8 @@ public class DashboardAdapterTest { final IconCache iconCache = new IconCache(context); mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */); ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); doReturn("another.package").when(context).getPackageName(); @@ -256,7 +261,8 @@ public class DashboardAdapterTest { when(iconCache.getIcon(tile.getIcon(context))).thenReturn(mock(RoundedHomepageIcon.class)); mDashboardAdapter = new DashboardAdapter(context, null /* savedInstanceState */, - null /* conditions */, null /* suggestionControllerMixin */, null /* lifecycle */); + null /* conditions */, null /* conditionManager */, + null /* suggestionControllerMixin */, null /* lifecycle */); ReflectionHelpers.setField(mDashboardAdapter, "mCache", iconCache); mDashboardAdapter.onBindTile(holder, tile); diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java new file mode 100644 index 00000000000..2f96eafb05b --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/ConditionManagerTest.java @@ -0,0 +1,134 @@ +/* + * 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.conditional.v2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import com.android.settings.homepage.conditional.ConditionListener; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +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; + +@RunWith(SettingsRobolectricTestRunner.class) +public class ConditionManagerTest { + + private static final long ID = 123L; + + @Mock + private ConditionalCard mCard; + @Mock + private ConditionalCardController mController; + @Mock + private ConditionListener mConditionListener; + + private Context mContext; + private ConditionManager mManager; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mManager = new ConditionManager(mContext, mConditionListener); + + assertThat(mManager.mCandidates.size()).isEqualTo(mManager.mCardControllers.size()); + + when(mController.getId()).thenReturn(ID); + when(mCard.getId()).thenReturn(ID); + + mManager.mCandidates.clear(); + mManager.mCardControllers.clear(); + mManager.mCandidates.add(mCard); + mManager.mCardControllers.add(mController); + } + + @Test + public void getDisplayableCards_nothingDisplayable() { + assertThat(mManager.getDisplayableCards()).isEmpty(); + } + + @Test + public void getDisplayableCards_hasDisplayable() { + when(mController.isDisplayable()).thenReturn(true); + + assertThat(mManager.getDisplayableCards()).hasSize(1); + } + + @Test + public void onPrimaryClick_shouldRelayToController() { + mManager.onPrimaryClick(mContext, ID); + + verify(mController).onPrimaryClick(mContext); + } + + @Test + public void onActionClick_shouldRelayToController() { + mManager.onActionClick(ID); + + verify(mController).onActionClick(); + } + + @Test + public void startMonitoringStateChange_multipleTimes_shouldRegisterOnce() { + mManager.startMonitoringStateChange(); + mManager.startMonitoringStateChange(); + mManager.startMonitoringStateChange(); + + verify(mController).startMonitoringStateChange(); + } + + @Test + public void stopMonitoringStateChange_beforeStart_shouldDoNothing() { + mManager.stopMonitoringStateChange(); + mManager.stopMonitoringStateChange(); + mManager.stopMonitoringStateChange(); + + verify(mController, never()).startMonitoringStateChange(); + verify(mController, never()).stopMonitoringStateChange(); + } + + @Test + public void stopMonitoringStateChange_multipleTimes_shouldUnregisterOnce() { + mManager.startMonitoringStateChange(); + + mManager.stopMonitoringStateChange(); + mManager.stopMonitoringStateChange(); + mManager.stopMonitoringStateChange(); + + verify(mController).startMonitoringStateChange(); + verify(mController).stopMonitoringStateChange(); + } + + @Test + public void onConditionChanged_shouldNotifyListener() { + mManager.onConditionChanged(); + + verify(mConditionListener).onConditionsChanged(); + } + +} diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java new file mode 100644 index 00000000000..fe4c621cdd4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardControllerTest.java @@ -0,0 +1,60 @@ +/* + * 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.conditional.v2; + + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +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; + +@RunWith(SettingsRobolectricTestRunner.class) +public class DndConditionalCardControllerTest { + + @Mock + private ConditionManager mConditionManager; + private Context mContext; + private DndConditionCardController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mController = new DndConditionCardController(mContext, mConditionManager); + } + + @Test + public void cycleMonitoring_shouldRegisterAndUnregisterReceiver() { + mController.startMonitoringStateChange(); + mController.stopMonitoringStateChange(); + + verify(mContext).registerReceiver(any(DndConditionCardController.Receiver.class), + eq(DndConditionCardController.DND_FILTER)); + verify(mContext).unregisterReceiver(any(DndConditionCardController.Receiver.class)); + } +} diff --git a/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java new file mode 100644 index 00000000000..1dfbd294a87 --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/conditional/v2/DndConditionalCardTest.java @@ -0,0 +1,62 @@ +/* + * 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.conditional.v2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +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; + +@RunWith(SettingsRobolectricTestRunner.class) +public class DndConditionalCardTest { + + @Mock + private ConditionManager mManager; + private DndConditionCardController mController; + + private Context mContext; + private DndConditionCard mCard; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + mController = new DndConditionCardController(mContext, mManager); + when(mManager.getController(anyLong())).thenReturn(mController); + + mCard = new DndConditionCard(mContext, mManager); + } + + @Test + public void getId_sameAsController() { + assertThat(mCard.getId()).isEqualTo(mController.getId()); + } + +}