diff options
author | Yuri Ufimtsev <yufimtsev@google.com> | 2023-05-12 15:09:17 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2023-05-12 15:09:17 +0000 |
commit | 9e22e62df20e43ea64aa5a2608f38071fd0ed0ef (patch) | |
tree | fdb85ecd0eb2a3d6ab2203560f92911cafd0aaf6 /PermissionController/src/com/android/permissioncontroller/safetycenter | |
parent | cea865007a8690a6cb288e8befb7906f5162fb38 (diff) | |
parent | 134aee7d974abee0e38e2e89aa2340b077136990 (diff) | |
download | Permission-9e22e62df20e43ea64aa5a2608f38071fd0ed0ef.tar.gz |
Merge "Allow SafetyCenter Subpages to scroll up after collapsing More issues" into udc-dev
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/safetycenter')
3 files changed, 136 insertions, 1 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java index dd90fd559..940cb2f69 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java @@ -41,6 +41,7 @@ import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceGroup; @@ -65,6 +66,7 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment { private static final String ISSUES_GROUP_KEY = "issues_group"; private static final String ENTRIES_GROUP_KEY = "entries_group"; private static final String STATIC_ENTRIES_GROUP_KEY = "static_entries_group"; + private static final String SPACER_KEY = "spacer"; private SafetyStatusPreference mSafetyStatusPreference; private final CollapsableGroupCardHelper mCollapsableGroupCardHelper = @@ -116,6 +118,8 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment { mEntriesGroup = null; getPreferenceScreen().removePreference(mStaticEntriesGroup); mStaticEntriesGroup = null; + Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY); + getPreferenceScreen().removePreference(spacerPreference); } getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus); diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt index 8625e1959..5e45d2b3c 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt @@ -105,7 +105,9 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { Log.w(TAG, "$sourceGroupId doesn't have any matching footer") subpageFooter.setVisible(false) } - + // footer is ordered last by default + // in order to keep a spacer after the footer, footer needs to be the second from last + subpageFooter.setOrder(Int.MAX_VALUE - 2) subpageFooter.setSummary(footerText) } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt new file mode 100644 index 000000000..bb09783be --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2023 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.permissioncontroller.safetycenter.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import androidx.preference.PreferenceViewHolder +import com.android.permissioncontroller.R +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity +import com.android.settingslib.widget.FooterPreference +import kotlin.math.max + +/** + * A preference that adds an empty space to the bottom of a Safety Center subpage. + * + * Due to the logic of [CollapsingToolbarBaseActivity], its content won't be scrollable if it fits + * the single page. This logic conflicts with the UX of collapsible and expandable items of Safety + * Center, and with some other use cases (i.e. opening the page from Search might scroll to bottom + * while the scroll is disabled). In such cases user won't be able to expand the collapsed toolbar + * by scrolling the screen content. + * + * [SpacerPreference] makes the page to be slightly bigger than the screen size to unlock the scroll + * regardless of the content length and to mitigate this UX problem. + * + * If a [FooterPreference] is added to the same [PreferenceScreen], its order should be decreased to + * keep it with the last visible content above the [SpacerPreference]. + */ +internal class SpacerPreference(context: Context, attrs: AttributeSet) : + Preference(context, attrs) { + + init { + setLayoutResource(R.layout.preference_spacer) + isVisible = SafetyCenterUiFlags.getShowSubpages() + // spacer should be the last item on screen + setOrder(Int.MAX_VALUE - 1) + } + + private var maxKnownToolbarHeight = 0 + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val spacer = holder.itemView + + // we should ensure we won't add multiple listeners to the same view, + // and Preferences API does not allow to do cleanups when onViewRecycled, + // so we are keeping a track of the added listener attaching it as a tag to the View + val listener: View.OnLayoutChangeListener = spacer.tag as? View.OnLayoutChangeListener + ?: object : View.OnLayoutChangeListener { + override fun onLayoutChange( + v: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + adjustHeight(spacer) + }}.also { spacer.tag = it } + + spacer.removeOnLayoutChangeListener(listener) + spacer.addOnLayoutChangeListener(listener) + } + + private fun adjustHeight(spacer: View) { + val root = spacer.rootView as? ViewGroup + if (root == null) { + return + } + + val contentParent = root.findViewById<ViewGroup>(R.id.content_parent) + if (contentParent == null) { + return + } + // when opening the Subpage from Search the layout pass may be triggered + // differently due to the auto-scroll to highlight a specific item, + // and in this case we need to wait the content parent to be measured + if (contentParent.height == 0) { + val globalLayoutObserver = object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + contentParent.viewTreeObserver.removeOnGlobalLayoutListener(this) + adjustHeight(spacer) + } + } + contentParent.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutObserver) + return + } + + val collapsingToolbar = root.findViewById<View>(R.id.collapsing_toolbar) + maxKnownToolbarHeight = max(maxKnownToolbarHeight, collapsingToolbar.height) + + val contentHeight = spacer.top + maxKnownToolbarHeight + val desiredSpacerHeight = if (contentHeight > contentParent.height) { + // making it 0 height will remove if from recyclerview + 1 + } else { + // to unlock the scrolling we need spacer to go slightly beyond the screen + contentParent.height - contentHeight + 1 + } + + val layoutParams = spacer.layoutParams + if (layoutParams.height != desiredSpacerHeight) { + layoutParams.height = desiredSpacerHeight + spacer.layoutParams = layoutParams + // need to let RecyclerView to update scroll position + spacer.post(::notifyChanged) + } + } +} |