1 /* 2 * Copyright (C) 2022 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 android.os; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 23 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.times; 30 31 import android.platform.test.annotations.Presubmit; 32 33 import androidx.test.ext.junit.runners.AndroidJUnit4; 34 import androidx.test.filters.SmallTest; 35 36 import com.android.dx.mockito.inline.extended.StaticMockitoSession; 37 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 import org.mockito.quality.Strictness; 41 import org.mockito.stubbing.Answer; 42 43 import java.util.Objects; 44 import java.util.concurrent.atomic.AtomicReference; 45 46 /** 47 * Test for verifying {@link android.os.Bundle} recycles the underlying parcel where needed. 48 * 49 * <p>Build/Install/Run: 50 * atest FrameworksMockingCoreTests:android.os.BundleRecyclingTest 51 */ 52 @RunWith(AndroidJUnit4.class) 53 @SmallTest 54 @Presubmit 55 public class BundleRecyclingTest { 56 private Parcel mParcelSpy; 57 private Bundle mBundle; 58 59 @Test bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce()60 public void bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce() { 61 setUpBundle(/* lazy */ 0, /* nonLazy */ 1); 62 // Will unparcel and immediately recycle parcel 63 assertNotNull(mBundle.getString("nonLazy0")); 64 verify(mParcelSpy, times(1)).recycle(); 65 assertFalse(mBundle.isDefinitelyEmpty()); 66 67 // Should not recycle again 68 mBundle.clear(); 69 verify(mParcelSpy, times(1)).recycle(); 70 assertTrue(mBundle.isDefinitelyEmpty()); 71 } 72 73 @Test bundleClear_whenParcelled_recyclesParcel()74 public void bundleClear_whenParcelled_recyclesParcel() { 75 setUpBundle(/* lazy */ 1); 76 assertTrue(mBundle.isParcelled()); 77 verify(mParcelSpy, times(0)).recycle(); 78 79 mBundle.clear(); 80 verify(mParcelSpy, times(1)).recycle(); 81 assertTrue(mBundle.isDefinitelyEmpty()); 82 83 // Should not recycle again 84 mBundle.clear(); 85 verify(mParcelSpy, times(1)).recycle(); 86 } 87 88 @Test bundleClear_whenUnparcelledWithLazy_recyclesParcel()89 public void bundleClear_whenUnparcelledWithLazy_recyclesParcel() { 90 setUpBundle(/* lazy */ 1); 91 92 // Will unparcel but keep the CustomParcelable lazy 93 assertFalse(mBundle.isEmpty()); 94 verify(mParcelSpy, times(0)).recycle(); 95 96 mBundle.clear(); 97 verify(mParcelSpy, times(1)).recycle(); 98 assertTrue(mBundle.isDefinitelyEmpty()); 99 100 // Should not recycle again 101 mBundle.clear(); 102 verify(mParcelSpy, times(1)).recycle(); 103 } 104 105 @Test bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel()106 public void bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel() { 107 setUpBundle(/* lazy */ 1); 108 109 Bundle copy = new Bundle(); 110 copy.putAll(mBundle); 111 112 mBundle.clear(); 113 assertTrue(mBundle.isDefinitelyEmpty()); 114 115 copy.clear(); 116 assertTrue(copy.isDefinitelyEmpty()); 117 118 verify(mParcelSpy, never()).recycle(); 119 } 120 121 @Test bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel()122 public void bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel() { 123 setUpBundle(/* lazy */ 1); 124 125 // Will unparcel but keep the CustomParcelable lazy 126 assertFalse(mBundle.isEmpty()); 127 128 Bundle copy = mBundle.deepCopy(); 129 copy.putAll(mBundle); 130 131 mBundle.clear(); 132 assertTrue(mBundle.isDefinitelyEmpty()); 133 134 copy.clear(); 135 assertTrue(copy.isDefinitelyEmpty()); 136 137 verify(mParcelSpy, never()).recycle(); 138 } 139 140 @Test bundleGet_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel()141 public void bundleGet_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel() { 142 setUpBundle(/* lazy */ 1); 143 144 // Will unparcel with a lazy value, and immediately unwrap the lazy value, 145 // with no lazy values left at the end of getParcelable 146 // Ref counting should immediately recycle 147 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 148 verify(mParcelSpy, times(1)).recycle(); 149 150 // Should not recycle again 151 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 152 mBundle.clear(); 153 verify(mParcelSpy, times(1)).recycle(); 154 } 155 156 @Test bundleGet_whenMultipleLazy_recyclesParcelWhenAllUnwrapped()157 public void bundleGet_whenMultipleLazy_recyclesParcelWhenAllUnwrapped() { 158 setUpBundle(/* lazy */ 2); 159 160 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 161 verify(mParcelSpy, times(0)).recycle(); 162 163 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 164 verify(mParcelSpy, times(0)).recycle(); 165 166 assertNotNull(mBundle.getParcelable("lazy1", CustomParcelable.class)); 167 verify(mParcelSpy, times(1)).recycle(); 168 169 // Should not recycle again 170 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 171 mBundle.clear(); 172 verify(mParcelSpy, times(1)).recycle(); 173 assertTrue(mBundle.isDefinitelyEmpty()); 174 } 175 176 @Test bundleGet_whenLazyAndNonLazy_recyclesParcelWhenAllUnwrapped()177 public void bundleGet_whenLazyAndNonLazy_recyclesParcelWhenAllUnwrapped() { 178 setUpBundle(/* lazy */ 1, /* nonLazy */ 1); 179 180 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 181 verify(mParcelSpy, times(1)).recycle(); 182 183 // Should not recycle again 184 assertNotNull(mBundle.getString("nonLazy0")); 185 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 186 mBundle.clear(); 187 verify(mParcelSpy, times(1)).recycle(); 188 } 189 190 @Test bundleGet_whenLazyAndNonLazy_doesNotRecycleWhenOnlyNonLazyRetrieved()191 public void bundleGet_whenLazyAndNonLazy_doesNotRecycleWhenOnlyNonLazyRetrieved() { 192 setUpBundle(/* lazy */ 1, /* nonLazy */ 1); 193 194 assertNotNull(mBundle.getString("nonLazy0")); 195 verify(mParcelSpy, times(0)).recycle(); 196 197 assertNotNull(mBundle.getString("nonLazy0")); 198 verify(mParcelSpy, times(0)).recycle(); 199 200 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 201 verify(mParcelSpy, times(1)).recycle(); 202 } 203 204 @Test bundleGet_withWithSharedParcel_doesNotRecycleParcel()205 public void bundleGet_withWithSharedParcel_doesNotRecycleParcel() { 206 setUpBundle(/* lazy */ 1); 207 208 Bundle copy = new Bundle(); 209 copy.putAll(mBundle); 210 211 assertNotNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 212 mBundle.clear(); 213 214 assertNotNull(copy.getParcelable("lazy0", CustomParcelable.class)); 215 copy.clear(); 216 217 verify(mParcelSpy, never()).recycle(); 218 } 219 220 @Test bundleGet_getAfterLazyCleared_doesNotRecycleAgain()221 public void bundleGet_getAfterLazyCleared_doesNotRecycleAgain() { 222 setUpBundle(/* lazy */ 1); 223 mBundle.clear(); 224 verify(mParcelSpy, times(1)).recycle(); 225 226 assertNull(mBundle.getParcelable("lazy0", CustomParcelable.class)); 227 verify(mParcelSpy, times(1)).recycle(); 228 } 229 setUpBundle(int lazy)230 private void setUpBundle(int lazy) { 231 setUpBundle(lazy, /* nonLazy */ 0); 232 } 233 setUpBundle(int lazy, int nonLazy)234 private void setUpBundle(int lazy, int nonLazy) { 235 AtomicReference<Parcel> parcel = new AtomicReference<>(); 236 StaticMockitoSession session = mockitoSession() 237 .strictness(Strictness.STRICT_STUBS) 238 .spyStatic(Parcel.class) 239 .startMocking(); 240 doAnswer((Answer<Parcel>) invocationOnSpy -> { 241 Parcel spy = (Parcel) invocationOnSpy.callRealMethod(); 242 spyOn(spy); 243 parcel.set(spy); 244 return spy; 245 }).when(() -> Parcel.obtain()); 246 247 Bundle bundle = new Bundle(); 248 bundle.setClassLoader(getClass().getClassLoader()); 249 Parcel p = createBundle(lazy, nonLazy); 250 bundle.readFromParcel(p); 251 p.recycle(); 252 253 session.finishMocking(); 254 255 mParcelSpy = parcel.get(); 256 mBundle = bundle; 257 } 258 259 /** 260 * Create a test bundle, parcel it and return the parcel. 261 */ createBundle(int lazy, int nonLazy)262 private Parcel createBundle(int lazy, int nonLazy) { 263 final Bundle source = new Bundle(); 264 265 for (int i = 0; i < nonLazy; i++) { 266 source.putString("nonLazy" + i, "Tiramisu"); 267 } 268 269 for (int i = 0; i < lazy; i++) { 270 source.putParcelable("lazy" + i, new CustomParcelable(13, "Tiramisu")); 271 } 272 273 return getParcelledBundle(source); 274 } 275 276 /** 277 * Take a bundle, write it to a parcel and return the parcel. 278 */ getParcelledBundle(Bundle bundle)279 private Parcel getParcelledBundle(Bundle bundle) { 280 final Parcel p = Parcel.obtain(); 281 // Don't use p.writeParcelabe(), which would write the creator, which we don't need. 282 bundle.writeToParcel(p, 0); 283 p.setDataPosition(0); 284 return p; 285 } 286 287 private static class CustomParcelable implements Parcelable { 288 public final int integer; 289 public final String string; 290 CustomParcelable(int integer, String string)291 CustomParcelable(int integer, String string) { 292 this.integer = integer; 293 this.string = string; 294 } 295 CustomParcelable(Parcel in)296 protected CustomParcelable(Parcel in) { 297 integer = in.readInt(); 298 string = in.readString(); 299 } 300 301 @Override describeContents()302 public int describeContents() { 303 return 0; 304 } 305 306 @Override writeToParcel(Parcel out, int flags)307 public void writeToParcel(Parcel out, int flags) { 308 out.writeInt(integer); 309 out.writeString(string); 310 } 311 312 @Override equals(Object other)313 public boolean equals(Object other) { 314 if (this == other) { 315 return true; 316 } 317 if (!(other instanceof CustomParcelable)) { 318 return false; 319 } 320 CustomParcelable that = (CustomParcelable) other; 321 return integer == that.integer && string.equals(that.string); 322 } 323 324 @Override hashCode()325 public int hashCode() { 326 return Objects.hash(integer, string); 327 } 328 329 public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() { 330 @Override 331 public CustomParcelable createFromParcel(Parcel in) { 332 return new CustomParcelable(in); 333 } 334 @Override 335 public CustomParcelable[] newArray(int size) { 336 return new CustomParcelable[size]; 337 } 338 }; 339 } 340 } 341