1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver.widget 18 19 import android.content.Context 20 import android.util.AttributeSet 21 import android.view.View 22 import android.widget.LinearLayout 23 import androidx.core.view.ScrollingView 24 import androidx.core.view.marginBottom 25 import androidx.core.view.marginLeft 26 import androidx.core.view.marginRight 27 import androidx.core.view.marginTop 28 import com.android.intentresolver.Flags.keyboardNavigationFix 29 30 /** 31 * A narrowly tailored [NestedScrollView] to be used inside [ResolverDrawerLayout] and help to 32 * orchestrate content preview scrolling. It expects one [LinearLayout] child with 33 * [LinearLayout.VERTICAL] orientation. If the child has more than one child, the first its child 34 * will be made scrollable (it is expected to be a content preview view). 35 */ 36 class ChooserNestedScrollView : NestedScrollView { 37 constructor(context: Context) : super(context) 38 39 constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 40 41 constructor( 42 context: Context, 43 attrs: AttributeSet?, 44 defStyleAttr: Int, 45 ) : super(context, attrs, defStyleAttr) 46 47 var requestChildFocusPredicate: (View?, View?) -> Boolean = DefaultChildFocusPredicate 48 49 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 50 val content = 51 getChildAt(0) as? LinearLayout ?: error("Exactly one child, LinerLayout, is expected") 52 require(content.orientation == LinearLayout.VERTICAL) { "VERTICAL orientation is expected" } 53 require(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { 54 "Expected to have an exact width" 55 } 56 57 val lp = content.layoutParams ?: error("LayoutParams is missing") 58 val contentWidthSpec = 59 getChildMeasureSpec( 60 widthMeasureSpec, 61 paddingLeft + content.marginLeft + content.marginRight + paddingRight, 62 lp.width, 63 ) 64 val contentHeightSpec = 65 getChildMeasureSpec( 66 heightMeasureSpec, 67 paddingTop + content.marginTop + content.marginBottom + paddingBottom, 68 lp.height, 69 ) 70 content.measure(contentWidthSpec, contentHeightSpec) 71 72 if (content.childCount > 1) { 73 // We expect that the first child should be scrollable up 74 val child = content.getChildAt(0) 75 val height = 76 MeasureSpec.getSize(heightMeasureSpec) + 77 child.measuredHeight + 78 child.marginTop + 79 child.marginBottom 80 81 content.measure( 82 contentWidthSpec, 83 MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec)), 84 ) 85 } 86 setMeasuredDimension( 87 MeasureSpec.getSize(widthMeasureSpec), 88 minOf( 89 MeasureSpec.getSize(heightMeasureSpec), 90 paddingTop + 91 content.marginTop + 92 content.measuredHeight + 93 content.marginBottom + 94 paddingBottom, 95 ), 96 ) 97 } 98 99 override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { 100 // let the parent scroll 101 super.onNestedPreScroll(target, dx, dy, consumed, type) 102 // scroll ourselves, if recycler has not scrolled 103 val delta = dy - consumed[1] 104 if (delta > 0 && target is ScrollingView && !target.canScrollVertically(-1)) { 105 val preScrollY = scrollY 106 scrollBy(0, delta) 107 consumed[1] += scrollY - preScrollY 108 } 109 } 110 111 override fun onRequestChildFocus(child: View?, focused: View?) { 112 if (keyboardNavigationFix()) { 113 if (requestChildFocusPredicate(child, focused)) { 114 super.onRequestChildFocus(child, focused) 115 } 116 } else { 117 super.onRequestChildFocus(child, focused) 118 } 119 } 120 121 companion object { 122 val DefaultChildFocusPredicate: (View?, View?) -> Boolean = { _, _ -> true } 123 } 124 } 125