1 /* 2 * Copyright (C) 2023 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.test.binder; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotEquals; 22 import static org.junit.Assert.assertTrue; 23 24 import android.content.Intent; 25 import android.os.Build; 26 import android.os.IBinder; 27 import android.os.RemoteException; 28 29 import androidx.test.core.app.ApplicationProvider; 30 import androidx.test.ext.junit.runners.AndroidJUnit4; 31 import androidx.test.rule.ServiceTestRule; 32 33 import org.junit.Rule; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 37 import java.lang.ref.PhantomReference; 38 import java.lang.ref.ReferenceQueue; 39 import java.util.concurrent.TimeoutException; 40 41 @RunWith(AndroidJUnit4.class) 42 public class BinderTest { 43 @Rule 44 public final ServiceTestRule serviceRule = new ServiceTestRule(); 45 46 @Test testDeathRecipientLeaksOrNot()47 public void testDeathRecipientLeaksOrNot() 48 throws RemoteException, TimeoutException, InterruptedException { 49 Intent intent = new Intent(ApplicationProvider.getApplicationContext(), MyService.class); 50 IFooProvider provider = IFooProvider.Stub.asInterface(serviceRule.bindService(intent)); 51 FooHolder holder = new FooHolder(provider.createFoo()); 52 53 // ref will get enqueued right after holder is finalized for gc. 54 ReferenceQueue<FooHolder> refQueue = new ReferenceQueue<>(); 55 PhantomReference<FooHolder> ref = new PhantomReference<>(holder, refQueue); 56 57 DeathRecorder deathRecorder = new DeathRecorder(); 58 holder.registerDeathRecorder(deathRecorder); 59 60 if (getSdkVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { 61 ///////////////////////////////////////////// 62 // New behavior 63 // 64 // Reference chain at this moment: 65 // holder --(java strong ref)--> FooHolder 66 // FooHolder.mProxy --(java strong ref)--> IFoo.Proxy 67 // IFoo.Proxy.mRemote --(java strong ref)--> BinderProxy 68 // BinderProxy --(binder ref)--> Foo.Stub 69 // In other words, the variable "holder" is the root of the reference chain. 70 71 // By setting the variable to null, we make FooHolder, IFoo.Proxy, BinderProxy, and even 72 // Foo.Stub unreachable. 73 holder = null; 74 75 // Ensure that the objects are garbage collected 76 forceGc(); 77 assertEquals(ref, refQueue.poll()); 78 assertTrue(provider.isFooGarbageCollected()); 79 80 // The binder has died, but we don't get notified since the death recipient is GC'ed. 81 provider.killProcess(); 82 Thread.sleep(1000); // give some time for the service process to die and reaped 83 assertFalse(deathRecorder.deathRecorded); 84 } else { 85 ///////////////////////////////////////////// 86 // Legacy behavior 87 // 88 // Reference chain at this moment: 89 // JavaDeathRecipient --(JNI strong ref)--> FooHolder 90 // holder --(java strong ref)--> FooHolder 91 // FooHolder.mProxy --(java strong ref)--> IFoo.Proxy 92 // IFoo.Proxy.mRemote --(java strong ref)--> BinderProxy 93 // BinderProxy --(binder ref)--> Foo.Stub 94 // So, BOTH JavaDeathRecipient and holder are roots of the reference chain. 95 96 // Even if we set holder to null, it doesn't make other objects unreachable; they are 97 // still reachable via the JNI strong ref. 98 holder = null; 99 100 // Check that objects are not garbage collected 101 forceGc(); 102 assertNotEquals(ref, refQueue.poll()); 103 assertFalse(provider.isFooGarbageCollected()); 104 105 // The legacy behavior is getting notified even when there's no reference 106 provider.killProcess(); 107 Thread.sleep(1000); // give some time for the service process to die and reaped 108 assertTrue(deathRecorder.deathRecorded); 109 } 110 } 111 112 static class FooHolder implements IBinder.DeathRecipient { 113 private IFoo mProxy; 114 private DeathRecorder mDeathRecorder; 115 FooHolder(IFoo proxy)116 FooHolder(IFoo proxy) throws RemoteException { 117 proxy.asBinder().linkToDeath(this, 0); 118 119 // A strong reference from DeathRecipient(this) to the binder proxy is created here 120 mProxy = proxy; 121 } 122 registerDeathRecorder(DeathRecorder dr)123 public void registerDeathRecorder(DeathRecorder dr) { 124 mDeathRecorder = dr; 125 } 126 127 @Override binderDied()128 public void binderDied() { 129 if (mDeathRecorder != null) { 130 mDeathRecorder.deathRecorded = true; 131 } 132 } 133 } 134 135 static class DeathRecorder { 136 public boolean deathRecorded = false; 137 } 138 139 // Try calling System.gc() until an orphaned object is confirmed to be finalized forceGc()140 private static void forceGc() { 141 Object obj = new Object(); 142 ReferenceQueue<Object> refQueue = new ReferenceQueue<>(); 143 PhantomReference<Object> ref = new PhantomReference<>(obj, refQueue); 144 obj = null; // make it an orphan 145 while (refQueue.poll() != ref) { 146 System.gc(); 147 } 148 } 149 getSdkVersion()150 private static int getSdkVersion() { 151 return ApplicationProvider.getApplicationContext().getApplicationInfo().targetSdkVersion; 152 } 153 } 154