Support contextual suggestion
- add a suggestion layout - add an interface to get fragment in suggestion feature provider - remove the first preference category of homepage IA to align its top with the bottom of the search box Bug: 173768418 Test: robotest Change-Id: I784e3eef29ca474c4c89f07b916c6500fabbf7d4
This commit is contained in:
@@ -57,6 +57,41 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:touchscreenBlocksFocus="false"
|
android:touchscreenBlocksFocus="false"
|
||||||
android:keyboardNavigationCluster="false">
|
android:keyboardNavigationCluster="false">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/app_bar_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/contextual_suggestion_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/suggestion_height"
|
||||||
|
android:paddingHorizontal="@dimen/suggestion_padding_horizontal"
|
||||||
|
android:paddingBottom="@dimen/suggestion_padding_bottom"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/suggestion_title"
|
||||||
|
android:text="@string/settings_label"
|
||||||
|
style="@style/ContextualSuggestionText"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/suggestion_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/suggestion_button_margin_top"
|
||||||
|
android:paddingHorizontal="@dimen/suggestion_button_padding_horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/ActionPrimaryButton"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<include layout="@layout/search_bar"/>
|
<include layout="@layout/search_bar"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
@@ -123,13 +123,20 @@
|
|||||||
<dimen name="switchbar_subsettings_margin_start">72dp</dimen>
|
<dimen name="switchbar_subsettings_margin_start">72dp</dimen>
|
||||||
<dimen name="switchbar_subsettings_margin_end">16dp</dimen>
|
<dimen name="switchbar_subsettings_margin_end">16dp</dimen>
|
||||||
|
|
||||||
|
<!-- Search bar and avatar -->
|
||||||
<dimen name="search_bar_margin">24dp</dimen>
|
<dimen name="search_bar_margin">24dp</dimen>
|
||||||
<dimen name="search_bar_height">48dp</dimen>
|
<dimen name="search_bar_height">48dp</dimen>
|
||||||
<dimen name="search_bar_text_size">16dp</dimen>
|
<dimen name="search_bar_text_size">16sp</dimen>
|
||||||
<dimen name="search_bar_card_elevation">2dp</dimen>
|
<dimen name="search_bar_card_elevation">2dp</dimen>
|
||||||
<dimen name="search_bar_content_inset">64dp</dimen>
|
<dimen name="search_bar_content_inset">64dp</dimen>
|
||||||
<dimen name="avatar_length">@dimen/search_bar_height</dimen>
|
<dimen name="avatar_length">@dimen/search_bar_height</dimen>
|
||||||
|
|
||||||
|
<!-- Contextual suggestions -->
|
||||||
|
<dimen name="suggestion_height">224dp</dimen>
|
||||||
|
<dimen name="suggestion_padding_horizontal">24dp</dimen>
|
||||||
|
<dimen name="suggestion_padding_bottom">8dp</dimen>
|
||||||
|
<dimen name="suggestion_button_margin_top">16dp</dimen>
|
||||||
|
<dimen name="suggestion_button_padding_horizontal">24dp</dimen>
|
||||||
|
|
||||||
<!-- Dimensions for Wifi Assistant Card -->
|
<!-- Dimensions for Wifi Assistant Card -->
|
||||||
<dimen name="wifi_assistant_padding_top_bottom">16dp</dimen>
|
<dimen name="wifi_assistant_padding_top_bottom">16dp</dimen>
|
||||||
|
@@ -782,6 +782,14 @@
|
|||||||
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
|
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="ContextualSuggestionText" parent="@android:style/TextAppearance.DeviceDefault">
|
||||||
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:textSize">32sp</item>
|
||||||
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
|
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="RequestManageCredentialsButtonPanel">
|
<style name="RequestManageCredentialsButtonPanel">
|
||||||
<item name="android:paddingStart">12dp</item>
|
<item name="android:paddingStart">12dp</item>
|
||||||
<item name="android:paddingEnd">12dp</item>
|
<item name="android:paddingEnd">12dp</item>
|
||||||
|
@@ -20,10 +20,6 @@
|
|||||||
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||||
android:key="top_level_settings_grouped">
|
android:key="top_level_settings_grouped">
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
android:key="connectivity"
|
|
||||||
android:order="-140"
|
|
||||||
settings:allowDividerAbove="false">
|
|
||||||
<Preference
|
<Preference
|
||||||
android:fragment="com.android.settings.network.NetworkDashboardFragment"
|
android:fragment="com.android.settings.network.NetworkDashboardFragment"
|
||||||
android:icon="@drawable/ic_homepage_network"
|
android:icon="@drawable/ic_homepage_network"
|
||||||
@@ -39,7 +35,6 @@
|
|||||||
android:order="-130"
|
android:order="-130"
|
||||||
android:title="@string/connected_devices_dashboard_title"
|
android:title="@string/connected_devices_dashboard_title"
|
||||||
settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>
|
settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="apps"
|
android:key="apps"
|
||||||
|
@@ -21,6 +21,7 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
/** Interface should be implemented if you have added new suggestions */
|
/** Interface should be implemented if you have added new suggestions */
|
||||||
public interface SuggestionFeatureProvider {
|
public interface SuggestionFeatureProvider {
|
||||||
@@ -42,4 +43,9 @@ public interface SuggestionFeatureProvider {
|
|||||||
* Returns the {@link SharedPreferences} that holds metadata for suggestions.
|
* Returns the {@link SharedPreferences} that holds metadata for suggestions.
|
||||||
*/
|
*/
|
||||||
SharedPreferences getSharedPrefs(Context context);
|
SharedPreferences getSharedPrefs(Context context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the class of {@link Fragment} that supports contextual suggestion.
|
||||||
|
*/
|
||||||
|
Class<? extends Fragment> getContextualSuggestionFragment();
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.android.settings.Settings.NightDisplaySuggestionActivity;
|
import com.android.settings.Settings.NightDisplaySuggestionActivity;
|
||||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollSuggestionActivity;
|
import com.android.settings.biometrics.fingerprint.FingerprintEnrollSuggestionActivity;
|
||||||
@@ -86,6 +87,11 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider
|
|||||||
return context.getSharedPreferences(SHARED_PREF_FILENAME, Context.MODE_PRIVATE);
|
return context.getSharedPreferences(SHARED_PREF_FILENAME, Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Fragment> getContextualSuggestionFragment() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public SuggestionFeatureProviderImpl(Context context) {
|
public SuggestionFeatureProviderImpl(Context context) {
|
||||||
final Context appContext = context.getApplicationContext();
|
final Context appContext = context.getApplicationContext();
|
||||||
mMetricsFeatureProvider = FeatureFactory.getFactory(appContext)
|
mMetricsFeatureProvider = FeatureFactory.getFactory(appContext)
|
||||||
|
@@ -21,6 +21,7 @@ import android.app.ActivityManager;
|
|||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.FeatureFlagUtils;
|
import android.util.FeatureFlagUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@@ -41,6 +42,9 @@ import com.android.settings.overlay.FeatureFactory;
|
|||||||
|
|
||||||
public class SettingsHomepageActivity extends FragmentActivity {
|
public class SettingsHomepageActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
private static final String TAG = "SettingsHomepageActivity";
|
||||||
|
private int mSearchBoxHeight;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -50,7 +54,9 @@ public class SettingsHomepageActivity extends FragmentActivity {
|
|||||||
root.setSystemUiVisibility(
|
root.setSystemUiVisibility(
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||||
|
|
||||||
setHomepageContainerPaddingTop();
|
final View appBar = findViewById(R.id.app_bar_container);
|
||||||
|
appBar.setMinimumHeight(getSearchBoxHeight());
|
||||||
|
setDefaultHomepageContainerPaddingTop();
|
||||||
|
|
||||||
final Toolbar toolbar = findViewById(R.id.search_action_bar);
|
final Toolbar toolbar = findViewById(R.id.search_action_bar);
|
||||||
FeatureFactory.getFactory(this).getSearchFeatureProvider()
|
FeatureFactory.getFactory(this).getSearchFeatureProvider()
|
||||||
@@ -60,16 +66,36 @@ public class SettingsHomepageActivity extends FragmentActivity {
|
|||||||
getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
|
getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
|
||||||
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
||||||
|
|
||||||
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)
|
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
|
||||||
&& !getSystemService(ActivityManager.class).isLowRamDevice()) {
|
// Only allow contextual features on high ram devices.
|
||||||
// Only allow contextual feature on high ram devices.
|
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.SILKY_HOME)) {
|
||||||
|
showSuggestionFragment();
|
||||||
|
}
|
||||||
|
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) {
|
||||||
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
|
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
showFragment(new TopLevelSettings(), R.id.main_content);
|
showFragment(new TopLevelSettings(), R.id.main_content);
|
||||||
((FrameLayout) findViewById(R.id.main_content))
|
((FrameLayout) findViewById(R.id.main_content))
|
||||||
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
|
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showSuggestionFragment() {
|
||||||
|
final Class<? extends Fragment> fragment = FeatureFactory.getFactory(this)
|
||||||
|
.getSuggestionFeatureProvider(this).getContextualSuggestionFragment();
|
||||||
|
if (fragment == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
showFragment(fragment.newInstance(), R.id.contextual_suggestion_content);
|
||||||
|
setHomepageContainerTopOffset(getResources()
|
||||||
|
.getDimensionPixelSize(R.dimen.suggestion_height));
|
||||||
|
} catch (IllegalAccessException | InstantiationException e) {
|
||||||
|
Log.w(TAG, "Cannot show fragment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void showFragment(Fragment fragment, int id) {
|
private void showFragment(Fragment fragment, int id) {
|
||||||
final FragmentManager fragmentManager = getSupportFragmentManager();
|
final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||||
@@ -84,18 +110,32 @@ public class SettingsHomepageActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setHomepageContainerPaddingTop() {
|
void setHomepageContainerTopOffset(int offset) {
|
||||||
final View view = this.findViewById(R.id.homepage_container);
|
final View view = findViewById(R.id.homepage_container);
|
||||||
|
final int paddingTop = getSearchBoxHeight() + offset;
|
||||||
final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
|
|
||||||
final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin);
|
|
||||||
|
|
||||||
// The top padding is the height of action bar(48dp) + top/bottom margins(16dp)
|
|
||||||
final int paddingTop = searchBarHeight + searchBarMargin * 2;
|
|
||||||
view.setPadding(0 /* left */, paddingTop, 0 /* right */, 0 /* bottom */);
|
view.setPadding(0 /* left */, paddingTop, 0 /* right */, 0 /* bottom */);
|
||||||
|
|
||||||
// Prevent inner RecyclerView gets focus and invokes scrolling.
|
// Prevent inner RecyclerView gets focus and invokes scrolling.
|
||||||
view.setFocusableInTouchMode(true);
|
view.setFocusableInTouchMode(true);
|
||||||
view.requestFocus();
|
view.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setDefaultHomepageContainerPaddingTop() {
|
||||||
|
setHomepageContainerTopOffset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
int getSearchBoxHeight() {
|
||||||
|
if (mSearchBoxHeight != 0) {
|
||||||
|
return mSearchBoxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
|
||||||
|
final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin);
|
||||||
|
|
||||||
|
// The height of search box is the height of search bar(48dp) + top/bottom margins(24dp)
|
||||||
|
mSearchBoxHeight = searchBarHeight + searchBarMargin * 2;
|
||||||
|
return mSearchBoxHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,25 +48,36 @@ import org.robolectric.util.ReflectionHelpers;
|
|||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class SettingsHomepageActivityTest {
|
public class SettingsHomepageActivityTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setHomepageContainerPaddingTop_shouldBeSetPaddingTop() {
|
public void setDefaultHomepageContainerPaddingTop_shouldSetSearchBoxHeight() {
|
||||||
final SettingsHomepageActivity activity = Robolectric.buildActivity(
|
final SettingsHomepageActivity activity = Robolectric.buildActivity(
|
||||||
SettingsHomepageActivity.class).create().get();
|
SettingsHomepageActivity.class).create().get();
|
||||||
final int searchBarHeight = activity.getResources().getDimensionPixelSize(
|
|
||||||
R.dimen.search_bar_height);
|
|
||||||
final int searchBarMargin = activity.getResources().getDimensionPixelSize(
|
|
||||||
R.dimen.search_bar_margin);
|
|
||||||
final View view = activity.findViewById(R.id.homepage_container);
|
final View view = activity.findViewById(R.id.homepage_container);
|
||||||
|
|
||||||
activity.setHomepageContainerPaddingTop();
|
activity.setDefaultHomepageContainerPaddingTop();
|
||||||
|
|
||||||
final int actualPaddingTop = view.getPaddingTop();
|
final int actualPaddingTop = view.getPaddingTop();
|
||||||
assertThat(actualPaddingTop).isEqualTo(searchBarHeight + searchBarMargin * 2);
|
assertThat(actualPaddingTop).isEqualTo(activity.getSearchBoxHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setHomepageContainerTopOffset_shouldBeSetPaddingTop() {
|
||||||
|
final SettingsHomepageActivity activity = Robolectric.buildActivity(
|
||||||
|
SettingsHomepageActivity.class).create().get();
|
||||||
|
final View view = activity.findViewById(R.id.homepage_container);
|
||||||
|
final int offset = activity.getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.suggestion_height);
|
||||||
|
|
||||||
|
activity.setHomepageContainerTopOffset(offset);
|
||||||
|
|
||||||
|
final int actualPaddingTop = view.getPaddingTop();
|
||||||
|
assertThat(actualPaddingTop).isEqualTo(activity.getSearchBoxHeight() + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Reference in New Issue
Block a user