• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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