1 /* 2 * Copyright (C) 2018 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.documentsui.queries; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertTrue; 23 import static org.mockito.Mockito.spy; 24 25 import static java.util.Objects.requireNonNull; 26 27 import android.content.Context; 28 import android.graphics.Bitmap; 29 import android.graphics.Canvas; 30 import android.graphics.drawable.Drawable; 31 import android.os.Bundle; 32 import android.platform.test.annotations.RequiresFlagsDisabled; 33 import android.platform.test.annotations.RequiresFlagsEnabled; 34 import android.platform.test.flag.junit.CheckFlagsRule; 35 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 36 import android.provider.DocumentsContract; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.view.ViewParent; 40 import android.widget.FrameLayout; 41 import android.widget.HorizontalScrollView; 42 import android.widget.LinearLayout; 43 44 import androidx.test.ext.junit.runners.AndroidJUnit4; 45 import androidx.test.filters.SmallTest; 46 import androidx.test.platform.app.InstrumentationRegistry; 47 48 import com.android.documentsui.IconUtils; 49 import com.android.documentsui.R; 50 import com.android.documentsui.base.MimeTypes; 51 import com.android.documentsui.base.Shared; 52 import com.android.documentsui.flags.Flags; 53 import com.android.documentsui.util.VersionUtils; 54 55 import com.google.android.material.chip.Chip; 56 57 import org.junit.Before; 58 import org.junit.Rule; 59 import org.junit.Test; 60 import org.junit.runner.RunWith; 61 62 import java.util.HashSet; 63 import java.util.Iterator; 64 import java.util.Set; 65 66 @RunWith(AndroidJUnit4.class) 67 @SmallTest 68 public final class SearchChipViewManagerTest { 69 70 private static final String LARGE_FILES_CHIP_MIME_TYPE = ""; 71 private static final String FROM_THIS_WEEK_CHIP_MIME_TYPE = ""; 72 private static final String[] TEST_MIME_TYPES_INCLUDING_DOCUMENT = 73 new String[]{"image/*", "video/*", "text/*"}; 74 private static final String[] TEST_MIME_TYPES = 75 new String[]{"image/*", "video/*"}; 76 private static final String[] TEST_OTHER_TYPES = 77 new String[]{LARGE_FILES_CHIP_MIME_TYPE, FROM_THIS_WEEK_CHIP_MIME_TYPE}; 78 private static int CHIP_TYPE = 1000; 79 80 private Context mContext; 81 private SearchChipViewManager mSearchChipViewManager; 82 private LinearLayout mChipGroup; 83 84 @Rule 85 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 86 87 @Before setUp()88 public void setUp() { 89 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 90 mContext.setTheme(com.android.documentsui.R.style.DocumentsTheme); 91 mContext.getTheme().applyStyle(R.style.DocumentsDefaultTheme, false); 92 mChipGroup = spy(new LinearLayout(mContext)); 93 94 mSearchChipViewManager = new SearchChipViewManager(mChipGroup); 95 mSearchChipViewManager.initChipSets(new String[] {"*/*"}); 96 } 97 98 @Test testInitChipSets_hasCorrectChipCount()99 public void testInitChipSets_hasCorrectChipCount() throws Exception { 100 mSearchChipViewManager.initChipSets(TEST_MIME_TYPES); 101 mSearchChipViewManager.updateChips(new String[] {"*/*"}); 102 103 int totalChipLength = TEST_MIME_TYPES.length + TEST_OTHER_TYPES.length; 104 assertThat(mChipGroup.getChildCount()).isEqualTo(totalChipLength); 105 } 106 107 @Test 108 @RequiresFlagsEnabled({Flags.FLAG_USE_MATERIAL3}) testChipIcon()109 public void testChipIcon() { 110 mSearchChipViewManager.initChipSets( 111 new String[] {"image/*", "audio/*", "video/*", "text/*"}); 112 mSearchChipViewManager.updateChips(new String[] {"*/*"}); 113 114 Chip imageChip = (Chip) mChipGroup.getChildAt(0); 115 assertDrawablesEqual( 116 requireNonNull(imageChip.getChipIcon()), 117 requireNonNull(mContext.getDrawable(R.drawable.ic_chip_image))); 118 Chip audioChip = (Chip) mChipGroup.getChildAt(1); 119 assertDrawablesEqual( 120 requireNonNull(audioChip.getChipIcon()), 121 requireNonNull(mContext.getDrawable(R.drawable.ic_chip_audio))); 122 Chip videoChip = (Chip) mChipGroup.getChildAt(2); 123 assertDrawablesEqual( 124 requireNonNull(videoChip.getChipIcon()), 125 requireNonNull(mContext.getDrawable(R.drawable.ic_chip_video))); 126 Chip documentChip = (Chip) mChipGroup.getChildAt(3); 127 assertDrawablesEqual( 128 requireNonNull(documentChip.getChipIcon()), 129 requireNonNull(mContext.getDrawable(R.drawable.ic_chip_document))); 130 } 131 132 @Test 133 @RequiresFlagsDisabled({Flags.FLAG_USE_MATERIAL3}) testChipIcon_M3Disabled()134 public void testChipIcon_M3Disabled() { 135 mSearchChipViewManager.initChipSets( 136 new String[] {"image/*", "audio/*", "video/*", "text/*"}); 137 mSearchChipViewManager.updateChips(new String[] {"*/*"}); 138 139 Chip imageChip = (Chip) mChipGroup.getChildAt(0); 140 assertDrawablesEqual( 141 requireNonNull(imageChip.getChipIcon()), 142 requireNonNull(IconUtils.loadMimeIcon(mContext, "image/*"))); 143 Chip audioChip = (Chip) mChipGroup.getChildAt(1); 144 assertDrawablesEqual( 145 requireNonNull(audioChip.getChipIcon()), 146 requireNonNull(IconUtils.loadMimeIcon(mContext, "audio/*"))); 147 Chip videoChip = (Chip) mChipGroup.getChildAt(2); 148 assertDrawablesEqual( 149 requireNonNull(videoChip.getChipIcon()), 150 requireNonNull(IconUtils.loadMimeIcon(mContext, "video/*"))); 151 Chip documentChip = (Chip) mChipGroup.getChildAt(3); 152 assertDrawablesEqual( 153 requireNonNull(documentChip.getChipIcon()), 154 requireNonNull(IconUtils.loadMimeIcon(mContext, MimeTypes.GENERIC_TYPE))); 155 } 156 157 @Test testUpdateChips_hasCorrectChipCount()158 public void testUpdateChips_hasCorrectChipCount() throws Exception { 159 mSearchChipViewManager.updateChips(TEST_MIME_TYPES); 160 161 int totalChipLength = TEST_MIME_TYPES.length + TEST_OTHER_TYPES.length; 162 assertThat(mChipGroup.getChildCount()).isEqualTo(totalChipLength); 163 } 164 165 @Test testUpdateChips_documentsFilterOnlyAvailableAboveR()166 public void testUpdateChips_documentsFilterOnlyAvailableAboveR() throws Exception { 167 mSearchChipViewManager.updateChips(TEST_MIME_TYPES_INCLUDING_DOCUMENT); 168 169 int totalChipLength = TEST_MIME_TYPES_INCLUDING_DOCUMENT.length + TEST_OTHER_TYPES.length; 170 if (VersionUtils.isAtLeastR()) { 171 assertThat(mChipGroup.getChildCount()).isEqualTo(totalChipLength); 172 } else { 173 assertThat(mChipGroup.getChildCount()).isEqualTo(totalChipLength - 1); 174 } 175 } 176 177 @Test testUpdateChips_withSingleMimeType_hasCorrectChipCount()178 public void testUpdateChips_withSingleMimeType_hasCorrectChipCount() throws Exception { 179 mSearchChipViewManager.updateChips(new String[]{"image/*"}); 180 181 assertThat(mChipGroup.getChildCount()).isEqualTo(TEST_OTHER_TYPES.length); 182 } 183 184 @Test testGetCheckedChipMimeTypes_hasCorrectValue()185 public void testGetCheckedChipMimeTypes_hasCorrectValue() throws Exception { 186 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 187 188 String[] checkedMimeTypes = 189 mSearchChipViewManager.getCheckedChipQueryArgs() 190 .getStringArray(DocumentsContract.QUERY_ARG_MIME_TYPES); 191 192 assertThat(MimeTypes.mimeMatches(TEST_MIME_TYPES, checkedMimeTypes[0])).isTrue(); 193 assertThat(MimeTypes.mimeMatches(TEST_MIME_TYPES, checkedMimeTypes[1])).isTrue(); 194 } 195 196 @Test testRestoreCheckedChipItems_hasCorrectValue()197 public void testRestoreCheckedChipItems_hasCorrectValue() throws Exception { 198 Bundle bundle = new Bundle(); 199 bundle.putIntArray(Shared.EXTRA_QUERY_CHIPS, new int[]{2}); 200 201 mSearchChipViewManager.restoreCheckedChipItems(bundle); 202 203 assertThat(mSearchChipViewManager.mCheckedChipItems.size()).isEqualTo(1); 204 Iterator<SearchChipData> iterator = mSearchChipViewManager.mCheckedChipItems.iterator(); 205 assertThat(iterator.next().getChipType()).isEqualTo(2); 206 } 207 208 @Test testSaveInstanceState_hasCorrectValue()209 public void testSaveInstanceState_hasCorrectValue() throws Exception { 210 mSearchChipViewManager.mCheckedChipItems = getFakeSearchChipDataList(); 211 Bundle bundle = new Bundle(); 212 213 mSearchChipViewManager.onSaveInstanceState(bundle); 214 215 final int[] chipTypes = bundle.getIntArray(Shared.EXTRA_QUERY_CHIPS); 216 assertThat(chipTypes.length).isEqualTo(1); 217 assertThat(chipTypes[0]).isEqualTo(CHIP_TYPE); 218 } 219 220 @Test testBindMirrorGroup_sameValue()221 public void testBindMirrorGroup_sameValue() throws Exception { 222 mSearchChipViewManager.updateChips(new String[] {"*/*"}); 223 224 ViewGroup mirror = spy(new LinearLayout(mContext)); 225 mSearchChipViewManager.bindMirrorGroup(mirror); 226 227 assertThat(View.VISIBLE).isEqualTo(mirror.getVisibility()); 228 assertThat(mChipGroup.getChildCount()).isEqualTo(mirror.getChildCount()); 229 assertThat(mChipGroup.getChildAt(0).getTag()).isEqualTo(mirror.getChildAt(0).getTag()); 230 } 231 232 @Test testBindMirrorGroup_showRow()233 public void testBindMirrorGroup_showRow() throws Exception { 234 mSearchChipViewManager.updateChips(new String[] {"image/*"}); 235 236 ViewGroup mirror = spy(new LinearLayout(mContext)); 237 mSearchChipViewManager.bindMirrorGroup(mirror); 238 239 assertThat(View.VISIBLE).isEqualTo(mirror.getVisibility()); 240 } 241 242 @Test testChipChecked_resetScroll()243 public void testChipChecked_resetScroll() { 244 // Mock chip group's parent chain according to search_chip_row.xml. 245 FrameLayout parent = spy(new FrameLayout(mContext)); 246 HorizontalScrollView grandparent = spy(new HorizontalScrollView(mContext)); 247 parent.addView(mChipGroup); 248 grandparent.addView(parent); 249 // Verify that getParent().getParent() returns the HorizontalScrollView mock. 250 ViewParent result = mChipGroup.getParent().getParent(); 251 assertEquals(grandparent, result); 252 253 mSearchChipViewManager.initChipSets( 254 new String[] {"image/*", "audio/*", "video/*", "text/*"}); 255 mSearchChipViewManager.updateChips(new String[] {"*/*"}); 256 257 // Manually set HorizontalScrollView's scrollX to something larger than 0. 258 grandparent.scrollTo(100, 0); 259 assertTrue(grandparent.getScaleX() > 0); 260 261 assertEquals(6, mChipGroup.getChildCount()); 262 Chip lastChip = (Chip) mChipGroup.getChildAt(5); 263 264 // chip.setChecked will trigger reorder animation, which needs to be run inside 265 // the looper thread. 266 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 267 // Check last chip will move it to the first child and reset scroll view. 268 lastChip.setChecked(true); 269 assertEquals(0, grandparent.getScrollX()); 270 }); 271 } 272 getFakeSearchChipDataList()273 private static Set<SearchChipData> getFakeSearchChipDataList() { 274 final Set<SearchChipData> chipDataList = new HashSet<>(); 275 chipDataList.add(new SearchChipData(CHIP_TYPE, 0 /* titleRes */, TEST_MIME_TYPES)); 276 return chipDataList; 277 } 278 assertDrawablesEqual(Drawable actual, Drawable expected)279 private void assertDrawablesEqual(Drawable actual, Drawable expected) { 280 Bitmap bitmap1 = 281 Bitmap.createBitmap( 282 actual.getIntrinsicWidth(), 283 actual.getIntrinsicHeight(), 284 Bitmap.Config.ARGB_8888); 285 Canvas canvas1 = new Canvas(bitmap1); 286 actual.setBounds(0, 0, actual.getIntrinsicWidth(), actual.getIntrinsicHeight()); 287 actual.draw(canvas1); 288 289 Bitmap bitmap2 = 290 Bitmap.createBitmap( 291 expected.getIntrinsicWidth(), 292 expected.getIntrinsicHeight(), 293 Bitmap.Config.ARGB_8888); 294 Canvas canvas2 = new Canvas(bitmap2); 295 expected.setBounds(0, 0, expected.getIntrinsicWidth(), expected.getIntrinsicHeight()); 296 expected.draw(canvas2); 297 298 assertTrue("Drawables are not equal", bitmap1.sameAs(bitmap2)); 299 } 300 } 301