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.profiles 18 19 import android.os.UserHandle 20 import android.view.LayoutInflater 21 import android.view.View 22 import android.view.ViewGroup 23 import android.widget.ListView 24 import androidx.test.platform.app.InstrumentationRegistry 25 import com.android.intentresolver.R 26 import com.android.intentresolver.ResolverListAdapter 27 import com.android.intentresolver.emptystate.EmptyStateProvider 28 import com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL 29 import com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK 30 import com.google.common.collect.ImmutableList 31 import com.google.common.truth.Truth.assertThat 32 import java.util.Optional 33 import java.util.function.Supplier 34 import org.junit.Test 35 import org.mockito.kotlin.doReturn 36 import org.mockito.kotlin.mock 37 38 class MultiProfilePagerAdapterTest { 39 private val PERSONAL_USER_HANDLE = UserHandle.of(10) 40 private val WORK_USER_HANDLE = UserHandle.of(20) 41 42 private val context = InstrumentationRegistry.getInstrumentation().getContext() 43 private val inflater = Supplier { 44 LayoutInflater.from(context).inflate(R.layout.resolver_list_per_profile, null, false) 45 as ViewGroup 46 } 47 48 @Test 49 fun testSinglePageProfileAdapter() { 50 val personalListAdapter = 51 mock<ResolverListAdapter> { on { userHandle } doReturn PERSONAL_USER_HANDLE } 52 val pagerAdapter = 53 MultiProfilePagerAdapter( 54 { listAdapter: ResolverListAdapter -> listAdapter }, 55 { listView: ListView, bindAdapter: ResolverListAdapter -> 56 listView.setAdapter(bindAdapter) 57 }, 58 ImmutableList.of( 59 TabConfig( 60 PROFILE_PERSONAL, 61 "personal", 62 "personal_a11y", 63 "TAG_PERSONAL", 64 personalListAdapter 65 ) 66 ), 67 object : EmptyStateProvider {}, 68 { false }, 69 PROFILE_PERSONAL, 70 null, 71 null, 72 inflater, 73 { Optional.empty() } 74 ) 75 assertThat(pagerAdapter.count).isEqualTo(1) 76 assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL) 77 assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE) 78 assertThat(pagerAdapter.getPageAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) 79 assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter) 80 assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) 81 assertThat(pagerAdapter.workListAdapter).isNull() 82 assertThat(pagerAdapter.itemCount).isEqualTo(1) 83 // TODO: consider covering some of the package-private methods (and making them public?). 84 // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter. 85 } 86 87 @Test 88 fun testTwoProfilePagerAdapter() { 89 val personalListAdapter = 90 mock<ResolverListAdapter> { on { userHandle } doReturn PERSONAL_USER_HANDLE } 91 val workListAdapter = 92 mock<ResolverListAdapter> { on { userHandle } doReturn WORK_USER_HANDLE } 93 val pagerAdapter = 94 MultiProfilePagerAdapter( 95 { listAdapter: ResolverListAdapter -> listAdapter }, 96 { listView: ListView, bindAdapter: ResolverListAdapter -> 97 listView.setAdapter(bindAdapter) 98 }, 99 ImmutableList.of( 100 TabConfig( 101 PROFILE_PERSONAL, 102 "personal", 103 "personal_a11y", 104 "TAG_PERSONAL", 105 personalListAdapter 106 ), 107 TabConfig(PROFILE_WORK, "work", "work_a11y", "TAG_WORK", workListAdapter) 108 ), 109 object : EmptyStateProvider {}, 110 { false }, 111 PROFILE_PERSONAL, 112 WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? 113 null, 114 inflater, 115 { Optional.empty() } 116 ) 117 assertThat(pagerAdapter.count).isEqualTo(2) 118 assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL) 119 assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE) 120 assertThat(pagerAdapter.getPageAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) 121 assertThat(pagerAdapter.getPageAdapterForIndex(1)).isSameInstanceAs(workListAdapter) 122 assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter) 123 assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) 124 assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter) 125 assertThat(pagerAdapter.itemCount).isEqualTo(2) 126 // TODO: consider covering some of the package-private methods (and making them public?). 127 // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter; 128 // especially matching profiles to ListViews? 129 // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected 130 // page changes. Currently there's no API to change the selected page directly; that's 131 // only possible through manipulation of the bound ViewPager. 132 } 133 134 @Test 135 fun testTwoProfilePagerAdapter_workIsDefault() { 136 val personalListAdapter = 137 mock<ResolverListAdapter> { on { userHandle } doReturn PERSONAL_USER_HANDLE } 138 val workListAdapter = 139 mock<ResolverListAdapter> { on { userHandle } doReturn WORK_USER_HANDLE } 140 val pagerAdapter = 141 MultiProfilePagerAdapter( 142 { listAdapter: ResolverListAdapter -> listAdapter }, 143 { listView: ListView, bindAdapter: ResolverListAdapter -> 144 listView.setAdapter(bindAdapter) 145 }, 146 ImmutableList.of( 147 TabConfig( 148 PROFILE_PERSONAL, 149 "personal", 150 "personal_a11y", 151 "TAG_PERSONAL", 152 personalListAdapter 153 ), 154 TabConfig(PROFILE_WORK, "work", "work_a11y", "TAG_WORK", workListAdapter) 155 ), 156 object : EmptyStateProvider {}, 157 { false }, 158 PROFILE_WORK, // <-- This test specifically requests we start on work profile. 159 WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? 160 null, 161 inflater, 162 { Optional.empty() } 163 ) 164 assertThat(pagerAdapter.count).isEqualTo(2) 165 assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_WORK) 166 assertThat(pagerAdapter.currentUserHandle).isEqualTo(WORK_USER_HANDLE) 167 assertThat(pagerAdapter.getPageAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) 168 assertThat(pagerAdapter.getPageAdapterForIndex(1)).isSameInstanceAs(workListAdapter) 169 assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(workListAdapter) 170 assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) 171 assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter) 172 assertThat(pagerAdapter.itemCount).isEqualTo(2) 173 // TODO: consider covering some of the package-private methods (and making them public?). 174 // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected 175 // page changes. Currently there's no API to change the selected page directly; that's 176 // only possible through manipulation of the bound ViewPager. 177 } 178 179 @Test 180 fun testBottomPaddingDelegate_default() { 181 val personalListAdapter = 182 mock<ResolverListAdapter> { on { userHandle } doReturn PERSONAL_USER_HANDLE } 183 val pagerAdapter = 184 MultiProfilePagerAdapter( 185 { listAdapter: ResolverListAdapter -> listAdapter }, 186 { listView: ListView, bindAdapter: ResolverListAdapter -> 187 listView.setAdapter(bindAdapter) 188 }, 189 ImmutableList.of( 190 TabConfig( 191 PROFILE_PERSONAL, 192 "personal", 193 "personal_a11y", 194 "TAG_PERSONAL", 195 personalListAdapter 196 ) 197 ), 198 object : EmptyStateProvider {}, 199 { false }, 200 PROFILE_PERSONAL, 201 null, 202 null, 203 inflater, 204 { Optional.empty() } 205 ) 206 val container = 207 pagerAdapter.activeEmptyStateView.requireViewById<View>( 208 com.android.internal.R.id.resolver_empty_state_container 209 ) 210 container.setPadding(1, 2, 3, 4) 211 pagerAdapter.setupContainerPadding() 212 assertThat(container.paddingLeft).isEqualTo(1) 213 assertThat(container.paddingTop).isEqualTo(2) 214 assertThat(container.paddingRight).isEqualTo(3) 215 assertThat(container.paddingBottom).isEqualTo(4) 216 } 217 218 @Test 219 fun testBottomPaddingDelegate_override() { 220 val personalListAdapter = 221 mock<ResolverListAdapter> { on { userHandle } doReturn PERSONAL_USER_HANDLE } 222 val pagerAdapter = 223 MultiProfilePagerAdapter( 224 { listAdapter: ResolverListAdapter -> listAdapter }, 225 { listView: ListView, bindAdapter: ResolverListAdapter -> 226 listView.setAdapter(bindAdapter) 227 }, 228 ImmutableList.of( 229 TabConfig( 230 PROFILE_PERSONAL, 231 "personal", 232 "personal_a11y", 233 "TAG_PERSONAL", 234 personalListAdapter 235 ) 236 ), 237 object : EmptyStateProvider {}, 238 { false }, 239 PROFILE_PERSONAL, 240 null, 241 null, 242 inflater, 243 { Optional.of(42) } 244 ) 245 val container = 246 pagerAdapter.activeEmptyStateView.requireViewById<View>( 247 com.android.internal.R.id.resolver_empty_state_container 248 ) 249 container.setPadding(1, 2, 3, 4) 250 pagerAdapter.setupContainerPadding() 251 assertThat(container.paddingLeft).isEqualTo(1) 252 assertThat(container.paddingTop).isEqualTo(2) 253 assertThat(container.paddingRight).isEqualTo(3) 254 assertThat(container.paddingBottom).isEqualTo(42) 255 } 256 257 @Test 258 fun testPresumedQuietModeEmptyStateForWorkProfile_whenQuiet() { 259 // TODO: this is "presumed" because the conditions to determine whether we "should" show an 260 // empty state aren't enforced to align with the conditions when we actually *would* -- I 261 // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider? 262 val personalListAdapter = 263 mock<ResolverListAdapter> { 264 on { userHandle } doReturn PERSONAL_USER_HANDLE 265 on { unfilteredCount } doReturn 1 266 } 267 val workListAdapter = 268 mock<ResolverListAdapter> { 269 on { userHandle } doReturn WORK_USER_HANDLE 270 on { unfilteredCount } doReturn 1 271 } 272 val pagerAdapter = 273 MultiProfilePagerAdapter( 274 { listAdapter: ResolverListAdapter -> listAdapter }, 275 { listView: ListView, bindAdapter: ResolverListAdapter -> 276 listView.setAdapter(bindAdapter) 277 }, 278 ImmutableList.of( 279 TabConfig( 280 PROFILE_PERSONAL, 281 "personal", 282 "personal_a11y", 283 "TAG_PERSONAL", 284 personalListAdapter 285 ), 286 TabConfig(PROFILE_WORK, "work", "work_a11y", "TAG_WORK", workListAdapter) 287 ), 288 object : EmptyStateProvider {}, 289 { true }, // <-- Work mode is quiet. 290 PROFILE_WORK, 291 WORK_USER_HANDLE, 292 null, 293 inflater, 294 { Optional.empty() } 295 ) 296 assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isTrue() 297 assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse() 298 } 299 300 @Test 301 fun testPresumedQuietModeEmptyStateForWorkProfile_notWhenNotQuiet() { 302 // TODO: this is "presumed" because the conditions to determine whether we "should" show an 303 // empty state aren't enforced to align with the conditions when we actually *would* -- I 304 // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider? 305 val personalListAdapter = 306 mock<ResolverListAdapter> { 307 on { userHandle } doReturn PERSONAL_USER_HANDLE 308 on { unfilteredCount } doReturn 1 309 } 310 val workListAdapter = 311 mock<ResolverListAdapter> { 312 on { userHandle } doReturn WORK_USER_HANDLE 313 on { unfilteredCount } doReturn 1 314 } 315 val pagerAdapter = 316 MultiProfilePagerAdapter( 317 { listAdapter: ResolverListAdapter -> listAdapter }, 318 { listView: ListView, bindAdapter: ResolverListAdapter -> 319 listView.setAdapter(bindAdapter) 320 }, 321 ImmutableList.of( 322 TabConfig( 323 PROFILE_PERSONAL, 324 "personal", 325 "personal_a11y", 326 "TAG_PERSONAL", 327 personalListAdapter 328 ), 329 TabConfig(PROFILE_WORK, "work", "work_a11y", "TAG_WORK", workListAdapter) 330 ), 331 object : EmptyStateProvider {}, 332 { false }, // <-- Work mode is not quiet. 333 PROFILE_WORK, 334 WORK_USER_HANDLE, 335 null, 336 inflater, 337 { Optional.empty() } 338 ) 339 assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isFalse() 340 assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse() 341 } 342 } 343