From 4c4a1f58c16d05c96dafae1047b44fe6e6a9a8c0 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 25 Mar 2022 21:39:02 +0800 Subject: [PATCH 1/9] Restrict secondary users to share Wi-Fi network - Remove "Share" and "Forget" options from the long press menu - Add SafetyNet Logging for security report Bug: 206986392 Test: manual test make RunSettingsRoboTests \ ROBOTEST_FILTER=NetworkProviderSettingsTest Change-Id: Ic434f0583cba557228c72508a501347ffa3141e1 Merged-In: Ic434f0583cba557228c72508a501347ffa3141e1 --- .../network/NetworkProviderSettings.java | 34 +++++- .../network/NetworkProviderSettingsTest.java | 100 +++++++++++++++++- 2 files changed, 127 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index 35a34d471a8..f7b88930190 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -33,9 +33,11 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; +import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.EventLog; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.ContextMenu; @@ -199,6 +201,8 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment * by the Test DPC tool in AFW mode. */ protected boolean mIsRestricted; + @VisibleForTesting + boolean mIsAdmin = true; @VisibleForTesting AirplaneModeEnabler mAirplaneModeEnabler; @@ -280,6 +284,13 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment addPreferences(); mIsRestricted = isUiRestricted(); + mIsAdmin = isAdminUser(); + } + + private boolean isAdminUser() { + final UserManager userManager = getSystemService(UserManager.class); + if (userManager == null) return true; + return userManager.isAdminUser(); } private void addPreferences() { @@ -538,7 +549,9 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment } if (mSelectedWifiEntry.canDisconnect()) { - menu.add(Menu.NONE, MENU_ID_SHARE, 0 /* order */, R.string.share); + if (mSelectedWifiEntry.canShare()) { + addShareMenuIfSuitable(menu); + } menu.add(Menu.NONE, MENU_ID_DISCONNECT, 1 /* order */, R.string.wifi_disconnect_button_text); } @@ -546,7 +559,7 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment // "forget" for normal saved network. And "disconnect" for ephemeral network because it // could only be disconnected and be put in blocklists so it won't be used again. if (canForgetNetwork()) { - menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); + addForgetMenuIfSuitable(menu); } WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration(); @@ -561,6 +574,23 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment } } + @VisibleForTesting + void addShareMenuIfSuitable(ContextMenu menu) { + if (mIsAdmin) { + menu.add(Menu.NONE, MENU_ID_SHARE, 0 /* order */, R.string.share); + return; + } + Log.w(TAG, "Don't add the Wi-Fi share menu because the user is not an admin."); + EventLog.writeEvent(0x534e4554, "206986392", -1 /* UID */, "User is not an admin"); + } + + @VisibleForTesting + void addForgetMenuIfSuitable(ContextMenu menu) { + if (mIsAdmin) { + menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); + } + } + private boolean canForgetNetwork() { return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(), mSelectedWifiEntry.getWifiConfiguration()); diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java index 19b044b7abf..38472c5e1c2 100644 --- a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java +++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java @@ -15,6 +15,9 @@ */ package com.android.settings.network; +import static com.android.settings.network.NetworkProviderSettings.MENU_ID_DISCONNECT; +import static com.android.settings.network.NetworkProviderSettings.MENU_ID_FORGET; +import static com.android.settings.network.NetworkProviderSettings.MENU_ID_SHARE; import static com.android.settings.wifi.WifiConfigUiBase2.MODE_CONNECT; import static com.android.settings.wifi.WifiConfigUiBase2.MODE_MODIFY; @@ -76,6 +79,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -105,6 +109,8 @@ public class NetworkProviderSettingsTest { @Mock private WifiPickerTracker mMockWifiPickerTracker; @Mock + private WifiEntry mWifiEntry; + @Mock private PreferenceManager mPreferenceManager; @Mock private InternetResetHelper mInternetResetHelper; @@ -113,6 +119,8 @@ public class NetworkProviderSettingsTest { @Mock private LayoutPreference mResetInternetPreference; @Mock + private ContextMenu mContextMenu; + @Mock private MenuItem mMenuItem; @Mock InternetUpdater mInternetUpdater; @@ -317,12 +325,54 @@ public class NetworkProviderSettingsTest { final View view = mock(View.class); when(view.getTag()).thenReturn(connectedWifiEntryPreference); - final ContextMenu menu = mock(ContextMenu.class); - mNetworkProviderSettings.onCreateContextMenu(menu, view, null /* info */); + mNetworkProviderSettings.onCreateContextMenu(mContextMenu, view, null /* info */); - verify(menu).add(anyInt(), eq(NetworkProviderSettings.MENU_ID_FORGET), anyInt(), anyInt()); - verify(menu).add(anyInt(), eq(NetworkProviderSettings.MENU_ID_DISCONNECT), anyInt(), - anyInt()); + verify(mContextMenu).add(anyInt(), eq(MENU_ID_FORGET), anyInt(), anyInt()); + verify(mContextMenu).add(anyInt(), eq(MENU_ID_DISCONNECT), anyInt(), anyInt()); + } + + @Test + public void onCreateContextMenu_canShare_shouldHaveShareMenuForConnectedWifiEntry() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.getApplicationContext()).thenReturn(mContext); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + + when(mWifiEntry.canDisconnect()).thenReturn(true); + when(mWifiEntry.canShare()).thenReturn(true); + when(mWifiEntry.canForget()).thenReturn(true); + when(mWifiEntry.isSaved()).thenReturn(true); + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + + final LongPressWifiEntryPreference connectedWifiEntryPreference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + final View view = mock(View.class); + when(view.getTag()).thenReturn(connectedWifiEntryPreference); + + mNetworkProviderSettings.onCreateContextMenu(mContextMenu, view, null /* info */); + + verify(mContextMenu).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); + } + + @Test + public void onCreateContextMenu_canNotShare_shouldDisappearShareMenuForConnectedWifiEntry() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.getApplicationContext()).thenReturn(mContext); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + + when(mWifiEntry.canDisconnect()).thenReturn(true); + when(mWifiEntry.canShare()).thenReturn(false); + when(mWifiEntry.canForget()).thenReturn(true); + when(mWifiEntry.isSaved()).thenReturn(true); + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + + final LongPressWifiEntryPreference connectedWifiEntryPreference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(mWifiEntry); + final View view = mock(View.class); + when(view.getTag()).thenReturn(connectedWifiEntryPreference); + + mNetworkProviderSettings.onCreateContextMenu(mContextMenu, view, null /* info */); + + verify(mContextMenu, never()).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); } @Test @@ -546,6 +596,46 @@ public class NetworkProviderSettingsTest { verify(mAirplaneModeEnabler).stop(); } + @Test + public void addShareMenuIfSuitable_isAdmin_addMenu() { + mNetworkProviderSettings.mIsAdmin = true; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addShareMenuIfSuitable(mContextMenu); + + verify(mContextMenu).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); + } + + @Test + public void addShareMenuIfSuitable_isNotAdmin_notAddMenu() { + mNetworkProviderSettings.mIsAdmin = false; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addShareMenuIfSuitable(mContextMenu); + + verify(mContextMenu, never()).add(anyInt(), eq(MENU_ID_SHARE), anyInt(), anyInt()); + } + + @Test + public void addForgetMenuIfSuitable_isAdmin_addMenu() { + mNetworkProviderSettings.mIsAdmin = true; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addForgetMenuIfSuitable(mContextMenu); + + verify(mContextMenu).add(anyInt(), eq(MENU_ID_FORGET), anyInt(), anyInt()); + } + + @Test + public void addForgetMenuIfSuitable_isNotAdmin_notAddMenu() { + mNetworkProviderSettings.mIsAdmin = false; + Mockito.reset(mContextMenu); + + mNetworkProviderSettings.addForgetMenuIfSuitable(mContextMenu); + + verify(mContextMenu, never()).add(anyInt(), eq(MENU_ID_FORGET), anyInt(), anyInt()); + } + @Implements(PreferenceFragmentCompat.class) public static class ShadowPreferenceFragmentCompat { From 906805a591dc9e5f162edab3ac4aeb2adec7cd0f Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Mon, 21 Mar 2022 22:16:42 +0800 Subject: [PATCH 2/9] Set title to the hearing aid pair instruction dialog Bug: 225117843 Test: make RunSettingsRoboTests ROBOTEST_FILTER=HearingAidDialogFragmentTest Change-Id: Ia1f3dadba84e3302a87aba1274e77f31a74eea1b --- .../HearingAidDialogFragment.java | 16 +--- .../HearingAidDialogFragmentTest.java | 93 +++++++++++++++++++ 2 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/accessibility/HearingAidDialogFragmentTest.java diff --git a/src/com/android/settings/accessibility/HearingAidDialogFragment.java b/src/com/android/settings/accessibility/HearingAidDialogFragment.java index 63633d8cdd6..107c56d8a8b 100644 --- a/src/com/android/settings/accessibility/HearingAidDialogFragment.java +++ b/src/com/android/settings/accessibility/HearingAidDialogFragment.java @@ -18,7 +18,6 @@ package com.android.settings.accessibility; import android.app.Dialog; import android.app.settings.SettingsEnums; -import android.content.DialogInterface; import android.os.Bundle; import androidx.appcompat.app.AlertDialog; @@ -30,24 +29,17 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment; public class HearingAidDialogFragment extends InstrumentedDialogFragment { public static HearingAidDialogFragment newInstance() { - HearingAidDialogFragment frag = new HearingAidDialogFragment(); - return frag; + return new HearingAidDialogFragment(); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.accessibility_hearingaid_pair_instructions_title) .setMessage(R.string.accessibility_hearingaid_pair_instructions_message) .setPositiveButton(R.string.accessibility_hearingaid_instruction_continue_button, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - launchBluetoothAddDeviceSetting(); - } - }) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { } - }) + (dialog, which) -> launchBluetoothAddDeviceSetting()) + .setNegativeButton(android.R.string.cancel, /* listener= */ null) .create(); } diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidDialogFragmentTest.java new file mode 100644 index 00000000000..4f8713ac5ba --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidDialogFragmentTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 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.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.SettingsActivity; +import com.android.settings.bluetooth.BluetoothPairingDetail; +import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowAlertDialogCompat.class) +public class HearingAidDialogFragmentTest { + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + private FragmentActivity mActivity; + private HearingAidDialogFragment mFragment; + + @Before + public void setUpTestFragment() { + mFragment = spy(HearingAidDialogFragment.newInstance()); + mActivity = Robolectric.setupActivity(FragmentActivity.class); + when(mFragment.getActivity()).thenReturn(mActivity); + } + + @Test + public void onCreateDialog_dialogExist() { + final Dialog dialog = mFragment.onCreateDialog(Bundle.EMPTY); + + assertThat(dialog).isNotNull(); + } + + @Test + public void dialogPositiveButtonClick_intentToExpectedClass() { + final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY); + dialog.show(); + + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + + final Intent intent = shadowOf(mActivity).getNextStartedActivity(); + assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) + .isEqualTo(BluetoothPairingDetail.class.getName()); + } + + @Test + public void dialogNegativeButtonClick_dismissDialog() { + final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY); + dialog.show(); + + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick(); + + assertThat(dialog.isShowing()).isFalse(); + } +} From 0bf3ed99eb338844dc9173794013b89113687151 Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Thu, 24 Mar 2022 19:31:07 +0800 Subject: [PATCH 3/9] Fix flickers on the Location page Use the UiBlocker and its delegate method to prevent the flicker. Fixes: 220837804 Test: Go to Settings > Location and see all items appearing at the same time. Change-Id: I2095289b9436ca66970b6a868d5c9780f2b19664 --- .../location/RecentLocationAccessPreferenceController.java | 6 +++++- ...ecentLocationAccessSeeAllButtonPreferenceController.java | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java index 18221d0f409..f4044ed03d1 100644 --- a/src/com/android/settings/location/RecentLocationAccessPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessPreferenceController.java @@ -131,12 +131,16 @@ public class RecentLocationAccessPreferenceController extends LocationBasePrefer banner.setSelectable(false); mCategoryRecentLocationRequests.addPreference(banner); } + + if (mUiBlockListener != null) { + mUiBlockListener.onBlockerWorkFinished(this); + } } @Override public void onLocationModeChanged(int mode, boolean restricted) { boolean enabled = mLocationEnabler.isEnabled(mode); - mCategoryRecentLocationRequests.setVisible(enabled); + updatePreferenceVisibilityDelegate(mCategoryRecentLocationRequests, enabled); } /** diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllButtonPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessSeeAllButtonPreferenceController.java index 68cde637eeb..b0dfae40c31 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllButtonPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllButtonPreferenceController.java @@ -20,11 +20,13 @@ import android.content.Context; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.core.BasePreferenceController; + /** * Preference controller that handles the "See All" button for recent location access. */ public class RecentLocationAccessSeeAllButtonPreferenceController extends - LocationBasePreferenceController { + LocationBasePreferenceController implements BasePreferenceController.UiBlocker { private Preference mPreference; @@ -44,6 +46,6 @@ public class RecentLocationAccessSeeAllButtonPreferenceController extends @Override public void onLocationModeChanged(int mode, boolean restricted) { boolean enabled = mLocationEnabler.isEnabled(mode); - mPreference.setVisible(enabled); + updatePreferenceVisibilityDelegate(mPreference, enabled); } } From 183c3f965b92c07de46ff096c8a0823e3697eaa7 Mon Sep 17 00:00:00 2001 From: Yuri Ufimtsev Date: Tue, 29 Mar 2022 15:09:33 +0000 Subject: [PATCH 4/9] Fix typo in Preference definition for Security Advanced Settings Test: manual Bug: 191735388 Change-Id: I9156a5d670e12a1249504cc9eb27ed1c2ef59bfc --- res/xml/security_advanced_settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml index 3d04d0164a4..dfc17ea70e6 100644 --- a/res/xml/security_advanced_settings.xml +++ b/res/xml/security_advanced_settings.xml @@ -22,7 +22,7 @@ + android:key="dashboard_tile_placeholder" /> Date: Tue, 29 Mar 2022 17:27:17 +0000 Subject: [PATCH 5/9] Home Controls: Add conditional copy for setting. Add conditional copy for when allow trivial devices setting is disabled. Add guidance on how to enable it. Bug: 227344249 Test: Manual and unit test Change-Id: I0f2f6dc933804ee6ba15dc180dc717d2a5b729a5 --- res/values/strings.xml | 2 ++ .../display/ControlsTrivialPrivacyPreferenceController.java | 3 +++ .../ControlsTrivialPrivacyPreferenceControllerTest.java | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 129cb8fa5cd..0fe19686f55 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13461,6 +13461,8 @@ Control from locked device Control external devices without unlocking your phone or tablet if allowed by the device controls app + + To use, first turn on \u0022Show device controls\u0022 Show double-line clock when available diff --git a/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java b/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java index a6c8e03d53c..57f717b9913 100644 --- a/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java +++ b/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceController.java @@ -50,6 +50,9 @@ public class ControlsTrivialPrivacyPreferenceController extends TogglePreference @Override public CharSequence getSummary() { + if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) { + return mContext.getText(R.string.lockscreen_trivial_disabled_controls_summary); + } return mContext.getText(R.string.lockscreen_trivial_controls_summary); } diff --git a/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java index 2c53d4e1457..3d4bc2e1933 100644 --- a/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/ControlsTrivialPrivacyPreferenceControllerTest.java @@ -109,7 +109,7 @@ public class ControlsTrivialPrivacyPreferenceControllerTest { Settings.Secure.putInt(mContentResolver, DEPENDENCY_SETTING_KEY, 0); assertThat(mController.getSummary().toString()).isEqualTo( - mContext.getText(R.string.lockscreen_trivial_controls_summary)); + mContext.getText(R.string.lockscreen_trivial_disabled_controls_summary)); } @Test From 680fce3acd37943b588ec044296cf982f79a4b45 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Thu, 24 Mar 2022 16:58:31 +0800 Subject: [PATCH 6/9] Refine layouts for large screen - Support dynamic paddings depending on app's screen width - Add round corners to homepage ripple effect to improve the transition of being highlighted - Add an interface to support dynamic split layout for suggestion cards Bug: 223300824 Test: robotest, manual Change-Id: Iaca6b4fd3f7369179416ef084a800d7eb2ee4640 --- .../homepage_highlighted_item_background.xml | 17 +-- .../homepage_selectable_item_background.xml | 27 ++++ res/layout/homepage_preference.xml | 21 ++- res/layout/search_bar.xml | 5 +- res/layout/search_bar_two_pane_version.xml | 9 +- ...tings_homepage_app_bar_two_pane_layout.xml | 2 - res/values-sw600dp/dimens.xml | 3 - res/values/dimens.xml | 19 ++- .../ActivityEmbeddingUtils.java | 10 ++ .../homepage/SettingsHomepageActivity.java | 121 +++++++++++++++--- .../homepage/SplitLayoutListener.java | 34 +++++ .../settings/homepage/TopLevelSettings.java | 104 ++++++++++++--- ...ighlightableTopLevelPreferenceAdapter.java | 31 ++--- .../settings/widget/HomepagePreference.java | 28 +++- .../HomepagePreferenceLayoutHelper.java | 78 +++++++++++ .../widget/RestrictedHomepagePreference.java | 28 +++- .../homepage/TopLevelSettingsTest.java | 2 + 17 files changed, 441 insertions(+), 98 deletions(-) create mode 100644 res/drawable/homepage_selectable_item_background.xml create mode 100644 src/com/android/settings/homepage/SplitLayoutListener.java create mode 100644 src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java diff --git a/res/drawable/homepage_highlighted_item_background.xml b/res/drawable/homepage_highlighted_item_background.xml index b7b24e991a1..d54ff601643 100644 --- a/res/drawable/homepage_highlighted_item_background.xml +++ b/res/drawable/homepage_highlighted_item_background.xml @@ -14,13 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - \ No newline at end of file + + + + \ No newline at end of file diff --git a/res/drawable/homepage_selectable_item_background.xml b/res/drawable/homepage_selectable_item_background.xml new file mode 100644 index 00000000000..f5e17ea69b7 --- /dev/null +++ b/res/drawable/homepage_selectable_item_background.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/res/layout/homepage_preference.xml b/res/layout/homepage_preference.xml index 62f6457d27f..59dc7c4b176 100644 --- a/res/layout/homepage_preference.xml +++ b/res/layout/homepage_preference.xml @@ -20,10 +20,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:minHeight="@dimen/homepage_preference_min_height" android:gravity="center_vertical" - android:paddingStart="@dimen/homepage_menu_entry_padding_horizontal" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:background="?android:attr/selectableItemBackground" android:clipToPadding="false" android:baselineAligned="false"> @@ -32,11 +30,10 @@ android:id="@+id/icon_frame" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minWidth="56dp" - android:gravity="center" + android:minWidth="48dp" + android:gravity="end|center_vertical" android:orientation="horizontal" - android:paddingStart="8dp" - android:paddingEnd="8dp" + android:paddingStart="@dimen/homepage_preference_icon_padding_start" android:paddingTop="4dp" android:paddingBottom="4dp"> @@ -44,18 +41,20 @@ android:id="@android:id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:maxWidth="40dp" - app:maxHeight="40dp"/> + app:maxWidth="48dp" + app:maxHeight="48dp"/> + android:paddingStart="@dimen/homepage_preference_text_padding_start" + android:paddingEnd="24dp"> - \ No newline at end of file + diff --git a/res/layout/search_bar.xml b/res/layout/search_bar.xml index 7cdf04dbd14..dfc9596d9fc 100644 --- a/res/layout/search_bar.xml +++ b/res/layout/search_bar.xml @@ -31,7 +31,8 @@ android:id="@+id/search_action_bar" android:layout_width="match_parent" android:layout_height="@dimen/search_bar_height" - android:paddingStart="4dp" + android:paddingStart="@dimen/search_bar_padding_start" + android:paddingEnd="@dimen/search_bar_padding_end" android:background="@drawable/search_bar_selected_background" android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset" android:navigationIcon="@drawable/ic_homepage_search"> @@ -40,7 +41,7 @@ style="@style/TextAppearance.SearchBar" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingStart="-4dp" + android:paddingStart="@dimen/search_bar_title_padding_start" android:layout_gravity="start" android:text="@string/search_menu"/> diff --git a/res/layout/search_bar_two_pane_version.xml b/res/layout/search_bar_two_pane_version.xml index a869853d4bc..ede57226699 100644 --- a/res/layout/search_bar_two_pane_version.xml +++ b/res/layout/search_bar_two_pane_version.xml @@ -21,20 +21,23 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:layout_margin="@dimen/search_bar_margin"> + android:layout_marginEnd="@dimen/search_bar_margin" + android:layout_marginVertical="@dimen/search_bar_margin"> diff --git a/res/layout/settings_homepage_app_bar_two_pane_layout.xml b/res/layout/settings_homepage_app_bar_two_pane_layout.xml index 4178632603a..4db2e652501 100644 --- a/res/layout/settings_homepage_app_bar_two_pane_layout.xml +++ b/res/layout/settings_homepage_app_bar_two_pane_layout.xml @@ -20,7 +20,6 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" android:background="?androidprv:attr/colorSurface" android:orientation="vertical"> @@ -41,7 +40,6 @@ android:layout_width="@dimen/avatar_length" android:layout_height="@dimen/avatar_length" android:layout_gravity="center" - android:layout_marginEnd="16dp" android:contentDescription="@string/search_bar_account_avatar_content_description"/> diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml index db1fbbc9be8..2e821e011e6 100755 --- a/res/values-sw600dp/dimens.xml +++ b/res/values-sw600dp/dimens.xml @@ -35,9 +35,6 @@ 80dp - 64dp - 32dp - 24dp 24dp diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 8a59da2844b..2e4d8be77f7 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -129,6 +129,12 @@ 20sp 28dp 56dp + 4dp + 8dp + 16dp + 24dp + -4dp + 8dp 48dp @@ -136,13 +142,16 @@ 24dp 20dp - + 8dp 24dp - - - 16dp - 28dp + 24dp + 28dp + 88sp + 32dp + 8dp + 16dp + 24dp 16dp diff --git a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java index 38af8d679cf..fdf13142cb8 100644 --- a/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java +++ b/src/com/android/settings/activityembedding/ActivityEmbeddingUtils.java @@ -16,6 +16,7 @@ package com.android.settings.activityembedding; +import android.app.Activity; import android.content.Context; import android.util.DisplayMetrics; import android.util.FeatureFlagUtils; @@ -33,6 +34,8 @@ public class ActivityEmbeddingUtils { // The smallest value of the smallest-width (sw) of the window in any rotation when // the split should be used. private static final float MIN_SMALLEST_SCREEN_SPLIT_WIDTH_DP = 600f; + // The minimum width of the activity to show the regular homepage layout. + private static final float MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP = 380f; private static final String TAG = "ActivityEmbeddingUtils"; /** Get the smallest pixel value of width of the window when the split should be used. */ @@ -71,4 +74,11 @@ public class ActivityEmbeddingUtils { return isFlagEnabled && isSplitSupported; } + + /** Whether to show the regular or simplified homepage layout. */ + public static boolean isRegularHomepageLayout(Activity activity) { + DisplayMetrics dm = activity.getResources().getDisplayMetrics(); + return dm.widthPixels >= (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, MIN_REGULAR_HOMEPAGE_LAYOUT_WIDTH_DP, dm); + } } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 7aeea114c39..038f6727c64 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -86,6 +86,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements private TopLevelSettings mMainFragment; private View mHomepageView; + private View mAppBar; private View mSuggestionView; private View mTwoPaneSuggestionView; private CategoryMixin mCategoryMixin; @@ -93,6 +94,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements private SplitController mSplitController; private boolean mIsEmbeddingActivityEnabled; private boolean mIsTwoPane; + // A regular layout shows icons on homepage, whereas a simplified layout doesn't. + private boolean mIsRegularLayout = true; /** A listener receiving homepage loaded events. */ public interface HomepageLoadedListener { @@ -100,8 +103,11 @@ public class SettingsHomepageActivity extends FragmentActivity implements void onHomepageLoaded(); } - private interface FragmentBuilder { - T build(); + private interface FragmentCreator { + T create(); + + /** To initialize after {@link #create} */ + default void init(Fragment fragment) {} } /** @@ -145,6 +151,11 @@ public class SettingsHomepageActivity extends FragmentActivity implements return mMainFragment; } + /** Whether the activity is showing in two-pane */ + public boolean isTwoPane() { + return mIsTwoPane; + } + @Override public CategoryMixin getCategoryMixin() { return mCategoryMixin; @@ -160,8 +171,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements mSplitController = SplitController.getInstance(); mIsTwoPane = mSplitController.isActivityEmbedded(this); - final View appBar = findViewById(R.id.app_bar_container); - appBar.setMinimumHeight(getSearchBoxHeight()); + mAppBar = findViewById(R.id.app_bar_container); + mAppBar.setMinimumHeight(getSearchBoxHeight()); initHomepageContainer(); updateHomepageAppBar(); updateHomepageBackground(); @@ -195,6 +206,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements // Launch the intent from deep link for large screen devices. launchDeepLinkIntentToRight(); + updateHomepagePaddings(); + updateSplitLayout(); } @Override @@ -226,7 +239,42 @@ public class SettingsHomepageActivity extends FragmentActivity implements mIsTwoPane = newTwoPaneState; updateHomepageAppBar(); updateHomepageBackground(); + updateHomepagePaddings(); } + updateSplitLayout(); + } + + private void updateSplitLayout() { + if (!mIsEmbeddingActivityEnabled) { + return; + } + + if (mIsTwoPane) { + if (mIsRegularLayout == ActivityEmbeddingUtils.isRegularHomepageLayout(this)) { + // Layout unchanged + return; + } + } else if (mIsRegularLayout) { + // One pane mode with the regular layout, not needed to change + return; + } + mIsRegularLayout = !mIsRegularLayout; + + // Update search title padding + View searchTitle = findViewById(R.id.search_bar_title); + if (searchTitle != null) { + int paddingStart = getResources().getDimensionPixelSize( + mIsRegularLayout + ? R.dimen.search_bar_title_padding_start_regular_two_pane + : R.dimen.search_bar_title_padding_start); + searchTitle.setPaddingRelative(paddingStart, 0, 0, 0); + } + // Notify fragments + getSupportFragmentManager().getFragments().forEach(fragment -> { + if (fragment instanceof SplitLayoutListener) { + ((SplitLayoutListener) fragment).onSplitLayoutChanged(mIsRegularLayout); + } + }); } private void setupEdgeToEdge() { @@ -303,29 +351,25 @@ public class SettingsHomepageActivity extends FragmentActivity implements // Schedule a timer to show the homepage and hide the suggestion on timeout. mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false), HOMEPAGE_LOADING_TIMEOUT_MS); - final FragmentBuilder fragmentBuilder = () -> { - try { - return fragmentClass.getConstructor().newInstance(); - } catch (Exception e) { - Log.w(TAG, "Cannot show fragment", e); - } - return null; - }; - showFragment(fragmentBuilder, R.id.suggestion_content); + showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ false), + R.id.suggestion_content); if (mIsEmbeddingActivityEnabled) { - showFragment(fragmentBuilder, R.id.two_pane_suggestion_content); + showFragment(new SuggestionFragCreator(fragmentClass, /* isTwoPaneLayout= */ true), + R.id.two_pane_suggestion_content); } } - private T showFragment(FragmentBuilder fragmentBuilder, int id) { + private T showFragment(FragmentCreator fragmentCreator, int id) { final FragmentManager fragmentManager = getSupportFragmentManager(); final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); T showFragment = (T) fragmentManager.findFragmentById(id); if (showFragment == null) { - showFragment = fragmentBuilder.build(); + showFragment = fragmentCreator.create(); + fragmentCreator.init(showFragment); fragmentTransaction.add(id, showFragment); } else { + fragmentCreator.init(showFragment); fragmentTransaction.show(showFragment); } fragmentTransaction.commit(); @@ -447,9 +491,54 @@ public class SettingsHomepageActivity extends FragmentActivity implements } } + private void updateHomepagePaddings() { + if (!mIsEmbeddingActivityEnabled) { + return; + } + if (mIsTwoPane) { + int padding = getResources().getDimensionPixelSize( + R.dimen.homepage_padding_horizontal_two_pane); + mAppBar.setPaddingRelative(padding, 0, padding, 0); + mMainFragment.setPaddingHorizontal(padding); + } else { + mAppBar.setPaddingRelative(0, 0, 0, 0); + mMainFragment.setPaddingHorizontal(0); + } + mMainFragment.updatePreferencePadding(mIsTwoPane); + } + private int getSearchBoxHeight() { final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height); final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin); return searchBarHeight + searchBarMargin * 2; } + + private static class SuggestionFragCreator implements FragmentCreator { + + private final Class mClass; + private final boolean mIsTwoPaneLayout; + + SuggestionFragCreator(Class clazz, boolean isTwoPaneLayout) { + mClass = clazz; + mIsTwoPaneLayout = isTwoPaneLayout; + } + + @Override + public Fragment create() { + try { + Fragment fragment = mClass.getConstructor().newInstance(); + return fragment; + } catch (Exception e) { + Log.w(TAG, "Cannot show fragment", e); + } + return null; + } + + @Override + public void init(Fragment fragment) { + if (fragment instanceof SplitLayoutListener) { + ((SplitLayoutListener) fragment).setSplitLayoutSupported(mIsTwoPaneLayout); + } + } + } } diff --git a/src/com/android/settings/homepage/SplitLayoutListener.java b/src/com/android/settings/homepage/SplitLayoutListener.java new file mode 100644 index 00000000000..63f4bc4e862 --- /dev/null +++ b/src/com/android/settings/homepage/SplitLayoutListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 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; + +/** A listener receiving the spilt layout change */ +public interface SplitLayoutListener { + + /** + * Called when the spilt layout is changed. + * + * @param isRegularLayout whether the layout should be regular or simplified + */ + void onSplitLayoutChanged(boolean isRegularLayout); + + /** + * Notifies the listener whether the split layout is supported. + */ + default void setSplitLayoutSupported(boolean supported) { + } +} diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index 334462b0b46..8623509ce9b 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -27,6 +27,8 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; +import android.view.LayoutInflater; +import android.view.ViewGroup; import androidx.fragment.app.Fragment; import androidx.preference.Preference; @@ -44,12 +46,13 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.support.SupportPreferenceController; import com.android.settings.widget.HomepagePreference; +import com.android.settings.widget.HomepagePreferenceLayoutHelper.HomepagePreferenceLayout; import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.drawer.Tile; import com.android.settingslib.search.SearchIndexable; @SearchIndexable(forTarget = MOBILE) -public class TopLevelSettings extends DashboardFragment implements +public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener, PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private static final String TAG = "TopLevelSettings"; @@ -58,6 +61,7 @@ public class TopLevelSettings extends DashboardFragment implements private boolean mIsEmbeddingActivityEnabled; private TopLevelHighlightMixin mHighlightMixin; + private int mPaddingHorizontal; private boolean mScrollNeeded = true; private boolean mFirstStarted = true; @@ -177,23 +181,13 @@ public class TopLevelSettings extends DashboardFragment implements @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); - final PreferenceScreen screen = getPreferenceScreen(); - if (screen == null) { - return; - } - // Tint the homepage icons - final int tintColor = Utils.getHomepageIconColor(getContext()); - final int count = screen.getPreferenceCount(); - for (int i = 0; i < count; i++) { - final Preference preference = screen.getPreference(i); - if (preference == null) { - break; - } - final Drawable icon = preference.getIcon(); + int tintColor = Utils.getHomepageIconColor(getContext()); + iteratePreferences(preference -> { + Drawable icon = preference.getIcon(); if (icon != null) { icon.setTint(tintColor); } - } + }); } @Override @@ -202,6 +196,15 @@ public class TopLevelSettings extends DashboardFragment implements highlightPreferenceIfNeeded(); } + @Override + public void onSplitLayoutChanged(boolean isRegularLayout) { + iteratePreferences(preference -> { + if (preference instanceof HomepagePreferenceLayout) { + ((HomepagePreferenceLayout) preference).getHelper().setIconVisible(isRegularLayout); + } + }); + } + @Override public void highlightPreferenceIfNeeded() { if (mHighlightMixin != null) { @@ -209,6 +212,52 @@ public class TopLevelSettings extends DashboardFragment implements } } + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle savedInstanceState) { + RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent, + savedInstanceState); + recyclerView.setPadding(mPaddingHorizontal, 0, mPaddingHorizontal, 0); + return recyclerView; + } + + /** Sets the horizontal padding */ + public void setPaddingHorizontal(int padding) { + mPaddingHorizontal = padding; + RecyclerView recyclerView = getListView(); + if (recyclerView != null) { + recyclerView.setPadding(padding, 0, padding, 0); + } + } + + /** Updates the preference internal paddings */ + public void updatePreferencePadding(boolean isTwoPane) { + iteratePreferences(new PreferenceJob() { + private int mIconPaddingStart; + private int mTextPaddingStart; + + @Override + public void init() { + mIconPaddingStart = getResources().getDimensionPixelSize(isTwoPane + ? R.dimen.homepage_preference_icon_padding_start_two_pane + : R.dimen.homepage_preference_icon_padding_start); + mTextPaddingStart = getResources().getDimensionPixelSize(isTwoPane + ? R.dimen.homepage_preference_text_padding_start_two_pane + : R.dimen.homepage_preference_text_padding_start); + } + + @Override + public void doForEach(Preference preference) { + if (preference instanceof HomepagePreferenceLayout) { + ((HomepagePreferenceLayout) preference).getHelper() + .setIconPaddingStart(mIconPaddingStart); + ((HomepagePreferenceLayout) preference).getHelper() + .setTextPaddingStart(mTextPaddingStart); + } + } + }); + } + /** Returns a {@link TopLevelHighlightMixin} that performs highlighting */ public TopLevelHighlightMixin getHighlightMixin() { return mHighlightMixin; @@ -261,6 +310,31 @@ public class TopLevelSettings extends DashboardFragment implements } } + private void iteratePreferences(PreferenceJob job) { + if (job == null || getPreferenceManager() == null) { + return; + } + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + return; + } + + job.init(); + int count = screen.getPreferenceCount(); + for (int i = 0; i < count; i++) { + Preference preference = screen.getPreference(i); + if (preference == null) { + break; + } + job.doForEach(preference); + } + } + + private interface PreferenceJob { + default void init() {} + void doForEach(Preference preference); + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.top_level_settings) { diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java index 911aabb13b8..338be483b6d 100644 --- a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java +++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java @@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; -import android.util.TypedValue; import android.view.View; import android.widget.ImageView; import android.widget.TextView; @@ -46,6 +45,10 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt private static final String TAG = "HighlightableTopLevelAdapter"; static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L; + private static final int RES_NORMAL_BACKGROUND = + R.drawable.homepage_selectable_item_background; + private static final int RES_HIGHLIGHTED_BACKGROUND = + R.drawable.homepage_highlighted_item_background; private final int mTitleColorNormal; private final int mTitleColorHighlight; @@ -54,11 +57,8 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt private final int mIconColorNormal; private final int mIconColorHighlight; - private final Context mContext; private final SettingsHomepageActivity mHomepageActivity; private final RecyclerView mRecyclerView; - private final int mNormalBackgroundRes; - private final int mHighlightBackgroundRes; private String mHighlightKey; private int mHighlightPosition = RecyclerView.NO_POSITION; private int mScrollPosition = RecyclerView.NO_POSITION; @@ -74,23 +74,18 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt mHighlightKey = key; mScrolled = !scrollNeeded; mViewHolders = new SparseArray<>(); - mContext = preferenceGroup.getContext(); mHomepageActivity = homepageActivity; - final TypedValue outValue = new TypedValue(); - mContext.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, - outValue, true /* resolveRefs */); - mNormalBackgroundRes = outValue.resourceId; - mHighlightBackgroundRes = R.drawable.homepage_highlighted_item_background; - mTitleColorNormal = Utils.getColorAttrDefaultColor(mContext, + Context context = preferenceGroup.getContext(); + mTitleColorNormal = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); - mTitleColorHighlight = Utils.getColorAttrDefaultColor(mContext, + mTitleColorHighlight = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse); - mSummaryColorNormal = Utils.getColorAttrDefaultColor(mContext, + mSummaryColorNormal = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary); - mSummaryColorHighlight = Utils.getColorAttrDefaultColor(mContext, + mSummaryColorHighlight = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse); - mIconColorNormal = Utils.getHomepageIconColor(mContext); - mIconColorHighlight = Utils.getHomepageIconColorHighlight(mContext); + mIconColorNormal = Utils.getHomepageIconColor(context); + mIconColorHighlight = Utils.getHomepageIconColorHighlight(context); } @Override @@ -236,7 +231,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt private void addHighlightBackground(PreferenceViewHolder holder) { final View v = holder.itemView; - v.setBackgroundResource(mHighlightBackgroundRes); + v.setBackgroundResource(RES_HIGHLIGHTED_BACKGROUND); ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight); ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight); final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); @@ -247,7 +242,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt private void removeHighlightBackground(PreferenceViewHolder holder) { final View v = holder.itemView; - v.setBackgroundResource(mNormalBackgroundRes); + v.setBackgroundResource(RES_NORMAL_BACKGROUND); ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal); ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal); final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); diff --git a/src/com/android/settings/widget/HomepagePreference.java b/src/com/android/settings/widget/HomepagePreference.java index ff4055ee226..d0780622631 100644 --- a/src/com/android/settings/widget/HomepagePreference.java +++ b/src/com/android/settings/widget/HomepagePreference.java @@ -20,29 +20,43 @@ import android.content.Context; import android.util.AttributeSet; import androidx.preference.Preference; - -import com.android.settings.R; +import androidx.preference.PreferenceViewHolder; /** A customized layout for homepage preference. */ -public class HomepagePreference extends Preference { +public class HomepagePreference extends Preference implements + HomepagePreferenceLayoutHelper.HomepagePreferenceLayout { + + private final HomepagePreferenceLayoutHelper mHelper; + public HomepagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); } public HomepagePreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); } public HomepagePreference(Context context, AttributeSet attrs) { super(context, attrs); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); } public HomepagePreference(Context context) { super(context); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + mHelper.onBindViewHolder(holder); + } + + @Override + public HomepagePreferenceLayoutHelper getHelper() { + return mHelper; } } diff --git a/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java b/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java new file mode 100644 index 00000000000..6242e23000c --- /dev/null +++ b/src/com/android/settings/widget/HomepagePreferenceLayoutHelper.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 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.widget; + +import android.view.View; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; + +/** Helper for homepage preference to manage layout. */ +public class HomepagePreferenceLayoutHelper { + + private View mIcon; + private View mText; + private boolean mIconVisible = true; + private int mIconPaddingStart = -1; + private int mTextPaddingStart = -1; + + /** The interface for managing preference layouts on homepage */ + public interface HomepagePreferenceLayout { + /** Returns a {@link HomepagePreferenceLayoutHelper} */ + HomepagePreferenceLayoutHelper getHelper(); + } + + public HomepagePreferenceLayoutHelper(Preference preference) { + preference.setLayoutResource(R.layout.homepage_preference); + } + + /** Sets whether the icon should be visible */ + public void setIconVisible(boolean visible) { + mIconVisible = visible; + if (mIcon != null) { + mIcon.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + /** Sets the icon padding start */ + public void setIconPaddingStart(int paddingStart) { + mIconPaddingStart = paddingStart; + if (mIcon != null && paddingStart >= 0) { + mIcon.setPaddingRelative(paddingStart, mIcon.getPaddingTop(), mIcon.getPaddingEnd(), + mIcon.getPaddingBottom()); + } + } + + /** Sets the text padding start */ + public void setTextPaddingStart(int paddingStart) { + mTextPaddingStart = paddingStart; + if (mText != null && paddingStart >= 0) { + mText.setPaddingRelative(paddingStart, mText.getPaddingTop(), mText.getPaddingEnd(), + mText.getPaddingBottom()); + } + } + + void onBindViewHolder(PreferenceViewHolder holder) { + mIcon = holder.findViewById(R.id.icon_frame); + mText = holder.findViewById(R.id.text_frame); + setIconVisible(mIconVisible); + setIconPaddingStart(mIconPaddingStart); + setTextPaddingStart(mTextPaddingStart); + } +} diff --git a/src/com/android/settings/widget/RestrictedHomepagePreference.java b/src/com/android/settings/widget/RestrictedHomepagePreference.java index 4667e2ce314..fbd6d4af097 100644 --- a/src/com/android/settings/widget/RestrictedHomepagePreference.java +++ b/src/com/android/settings/widget/RestrictedHomepagePreference.java @@ -19,29 +19,45 @@ package com.android.settings.widget; import android.content.Context; import android.util.AttributeSet; -import com.android.settings.R; +import androidx.preference.PreferenceViewHolder; + import com.android.settingslib.RestrictedTopLevelPreference; /** Homepage preference that can be disabled by a device admin using a user restriction. */ -public class RestrictedHomepagePreference extends RestrictedTopLevelPreference { +public class RestrictedHomepagePreference extends RestrictedTopLevelPreference implements + HomepagePreferenceLayoutHelper.HomepagePreferenceLayout { + + private final HomepagePreferenceLayoutHelper mHelper; + public RestrictedHomepagePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); } public RestrictedHomepagePreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); } public RestrictedHomepagePreference(Context context, AttributeSet attrs) { super(context, attrs); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); } public RestrictedHomepagePreference(Context context) { super(context); - setLayoutResource(R.layout.homepage_preference); + mHelper = new HomepagePreferenceLayoutHelper(this); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + mHelper.onBindViewHolder(holder); + } + + @Override + public HomepagePreferenceLayoutHelper getHelper() { + return mHelper; } } diff --git a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java index da2f8b5e566..44f44aa8589 100644 --- a/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java +++ b/tests/robotests/src/com/android/settings/homepage/TopLevelSettingsTest.java @@ -30,6 +30,7 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import androidx.preference.Preference; +import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import com.android.settings.R; @@ -73,6 +74,7 @@ public class TopLevelSettingsTest { doReturn(1).when(screen).getPreferenceCount(); doReturn(preference).when(screen).getPreference(anyInt()); doReturn(screen).when(mSettings).getPreferenceScreen(); + doReturn(new PreferenceManager(mContext)).when(mSettings).getPreferenceManager(); doReturn(0).when(mSettings).getPreferenceScreenResId(); mSettings.onCreatePreferences(new Bundle(), "rootKey"); From b388ccfc70ef7082d0d22ff1a86a53aed398b16f Mon Sep 17 00:00:00 2001 From: Yuri Ufimtsev Date: Wed, 30 Mar 2022 07:57:38 +0000 Subject: [PATCH 7/9] Remove title of Smart Lock / Trust Agents preference category Test: manual Bug: 191735388 Change-Id: I49a0f8138dc06e4bc4ab945116bde5e2e83ffaff --- res/xml/security_advanced_settings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml index dfc17ea70e6..6d43fe77451 100644 --- a/res/xml/security_advanced_settings.xml +++ b/res/xml/security_advanced_settings.xml @@ -27,7 +27,8 @@ + android:key="security_category" + android:layout="@layout/preference_category_no_label" /> Date: Wed, 23 Mar 2022 14:17:20 +0000 Subject: [PATCH 8/9] Fix "Remove Guest" option shows "Reset Guest" dialog If the guest user was not auto created, removing the user from settings message was wrongly worded. Small refactor to reuse the same wording in different modules has been done. Bug: 225314166 Test: manual Change-Id: Ia1871efc6aeeb2d3604c2fc4fd98a2a3a500953b --- .../settings/users/UserDetailsSettings.java | 7 ++++++- .../android/settings/users/UserDialogs.java | 21 +++++++++++++++++++ .../android/settings/users/UserSettings.java | 7 ++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index f60aec73d2c..99760401d29 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -209,8 +209,13 @@ public class UserDetailsSettings extends SettingsPreferenceFragment } }); case DIALOG_CONFIRM_RESET_GUEST: - return UserDialogs.createResetGuestDialog(getActivity(), + if (mGuestUserAutoCreated) { + return UserDialogs.createResetGuestDialog(getActivity(), (dialog, which) -> resetGuest()); + } else { + return UserDialogs.createRemoveGuestDialog(getActivity(), + (dialog, which) -> resetGuest()); + } } throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } diff --git a/src/com/android/settings/users/UserDialogs.java b/src/com/android/settings/users/UserDialogs.java index d28b7985330..8549ffef407 100644 --- a/src/com/android/settings/users/UserDialogs.java +++ b/src/com/android/settings/users/UserDialogs.java @@ -196,4 +196,25 @@ public final class UserDialogs { .setNegativeButton(android.R.string.cancel, null) .create(); } + + + /** + * Creates a dialog to confirm with the user if it's ok to remove the guest user, which will + * delete all the guest user's data. + * + * @param context a Context object + * @param onConfirmListener Callback object for positive action + * @return the created Dialog + */ + public static Dialog createRemoveGuestDialog(Context context, + DialogInterface.OnClickListener onConfirmListener) { + return new AlertDialog.Builder(context) + .setTitle(com.android.settingslib.R.string.guest_remove_guest_dialog_title) + .setMessage(R.string.user_exit_guest_confirm_message) + .setPositiveButton( + com.android.settingslib.R.string.guest_remove_guest_confirm_button, + onConfirmListener) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } } diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 88959121437..358b87b9abf 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -737,8 +737,13 @@ public class UserSettings extends SettingsPreferenceFragment return buildAddUserDialog(USER_TYPE_RESTRICTED_PROFILE); } case DIALOG_CONFIRM_RESET_GUEST: { - return UserDialogs.createResetGuestDialog(getActivity(), + if (mGuestUserAutoCreated) { + return UserDialogs.createResetGuestDialog(getActivity(), (dialog, which) -> resetGuest()); + } else { + return UserDialogs.createRemoveGuestDialog(getActivity(), + (dialog, which) -> resetGuest()); + } } default: return null; From 75063d8474f9aecaab4a01a9e7bf5400a767ea14 Mon Sep 17 00:00:00 2001 From: Joshua Mccloskey Date: Wed, 30 Mar 2022 00:34:46 +0000 Subject: [PATCH 9/9] Added vibration back to UDFPS#onEnrollmentProgress Test: Verified haptic is now present. Bug: 222593183 Change-Id: I3aed905e127f4e3eff931e4e35f5929389764b7e --- .../fingerprint/FingerprintEnrollEnrolling.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index f41316a75c7..dfb4a347ec0 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -112,6 +112,12 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + + private static final VibrationEffect SUCCESS_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + private FingerprintManager mFingerprintManager; private boolean mCanAssumeUdfps; @Nullable private ProgressBar mProgressBar; @@ -509,6 +515,14 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { mErrorText.removeCallbacks(mTouchAgainRunnable); mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION); } else { + if (mVibrator != null) { + mVibrator.vibrate(Process.myUid(), + getApplicationContext().getOpPackageName(), + SUCCESS_VIBRATION_EFFECT, + getClass().getSimpleName() + "::OnEnrollmentProgressChanged", + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + } + if (mIsAccessibilityEnabled) { final int percent = (int) (((float)(steps - remaining) / (float) steps) * 100); CharSequence cs = getString( @@ -582,7 +596,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { } if (isResumed() && (mIsAccessibilityEnabled || !mCanAssumeUdfps)) { mVibrator.vibrate(Process.myUid(), getApplicationContext().getOpPackageName(), - VIBRATE_EFFECT_ERROR, "FingerprintEnrollEnrolling:showError", + VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::showError", FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES); } }