• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.systemui.shared.plugins;
18 
19 import static junit.framework.Assert.assertEquals;
20 import static junit.framework.Assert.assertNotNull;
21 import static junit.framework.Assert.assertNull;
22 
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.test.suitebuilder.annotation.SmallTest;
27 
28 import androidx.test.runner.AndroidJUnit4;
29 
30 import com.android.systemui.SysuiTestCase;
31 import com.android.systemui.plugins.Plugin;
32 import com.android.systemui.plugins.PluginLifecycleManager;
33 import com.android.systemui.plugins.PluginListener;
34 import com.android.systemui.plugins.annotations.ProvidesInterface;
35 import com.android.systemui.plugins.annotations.Requires;
36 
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 
41 import java.lang.ref.WeakReference;
42 import java.util.Collections;
43 import java.util.concurrent.atomic.AtomicInteger;
44 
45 @SmallTest
46 @RunWith(AndroidJUnit4.class)
47 public class PluginInstanceTest extends SysuiTestCase {
48 
49     private static final String PRIVILEGED_PACKAGE = "com.android.systemui.plugins";
50     private static final ComponentName TEST_PLUGIN_COMPONENT_NAME =
51             new ComponentName(PRIVILEGED_PACKAGE, TestPluginImpl.class.getName());
52 
53     private FakeListener mPluginListener;
54     private VersionInfo mVersionInfo;
55     private VersionInfo.InvalidVersionException mVersionException;
56     private PluginInstance.VersionChecker mVersionChecker;
57 
58     private RefCounter mCounter;
59     private PluginInstance<TestPlugin> mPluginInstance;
60     private PluginInstance.Factory mPluginInstanceFactory;
61     private ApplicationInfo mAppInfo;
62 
63     // Because we're testing memory in this file, we must be careful not to assert the target
64     // objects, or capture them via mockito if we expect the garbage collector to later free them.
65     // Both JUnit and Mockito will save references and prevent these objects from being cleaned up.
66     private WeakReference<TestPluginImpl> mPlugin;
67     private WeakReference<Context> mPluginContext;
68 
69     @Before
setup()70     public void setup() throws Exception {
71         mCounter = new RefCounter();
72         mAppInfo = mContext.getApplicationInfo();
73         mAppInfo.packageName = TEST_PLUGIN_COMPONENT_NAME.getPackageName();
74         mPluginListener = new FakeListener();
75         mVersionInfo = new VersionInfo();
76         mVersionChecker = new PluginInstance.VersionChecker() {
77             @Override
78             public <T extends Plugin> VersionInfo checkVersion(
79                     Class<T> instanceClass,
80                     Class<T> pluginClass,
81                     Plugin plugin
82             ) {
83                 if (mVersionException != null) {
84                     throw mVersionException;
85                 }
86                 return mVersionInfo;
87             }
88         };
89 
90         mPluginInstanceFactory = new PluginInstance.Factory(
91                 this.getClass().getClassLoader(),
92                 new PluginInstance.InstanceFactory<TestPlugin>() {
93                     @Override
94                     TestPlugin create(Class cls) {
95                         TestPluginImpl plugin = new TestPluginImpl(mCounter);
96                         mPlugin = new WeakReference<>(plugin);
97                         return plugin;
98                     }
99                 },
100                 mVersionChecker,
101                 Collections.singletonList(PRIVILEGED_PACKAGE),
102                 false);
103 
104         mPluginInstance = mPluginInstanceFactory.create(
105                 mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
106                 TestPlugin.class, mPluginListener);
107         mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
108     }
109 
110     @Test
testCorrectVersion()111     public void testCorrectVersion() {
112         assertNotNull(mPluginInstance);
113     }
114 
115     @Test(expected = VersionInfo.InvalidVersionException.class)
testIncorrectVersion()116     public void testIncorrectVersion() throws Exception {
117         ComponentName wrongVersionTestPluginComponentName =
118                 new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
119 
120         mVersionException = new VersionInfo.InvalidVersionException("test", true);
121 
122         mPluginInstanceFactory.create(
123                 mContext, mAppInfo, wrongVersionTestPluginComponentName,
124                 TestPlugin.class, mPluginListener);
125         mPluginInstance.onCreate();
126     }
127 
128     @Test
testOnCreate()129     public void testOnCreate() {
130         mPluginInstance.onCreate();
131         assertEquals(1, mPluginListener.mAttachedCount);
132         assertEquals(1, mPluginListener.mLoadCount);
133         assertEquals(mPlugin.get(), mPluginInstance.getPlugin());
134         assertInstances(1, 1);
135     }
136 
137     @Test
testOnDestroy()138     public void testOnDestroy() {
139         mPluginInstance.onCreate();
140         mPluginInstance.onDestroy();
141         assertEquals(1, mPluginListener.mDetachedCount);
142         assertEquals(1, mPluginListener.mUnloadCount);
143         assertNull(mPluginInstance.getPlugin());
144         assertInstances(0, 0); // Destroyed but never created
145     }
146 
147     @Test
testOnRepeatedlyLoadUnload_PluginFreed()148     public void testOnRepeatedlyLoadUnload_PluginFreed() {
149         mPluginInstance.onCreate();
150         mPluginInstance.loadPlugin();
151         assertInstances(1, 1);
152 
153         mPluginInstance.unloadPlugin();
154         assertNull(mPluginInstance.getPlugin());
155         assertInstances(0, 0);
156 
157         mPluginInstance.loadPlugin();
158         assertInstances(1, 1);
159 
160         mPluginInstance.unloadPlugin();
161         mPluginInstance.onDestroy();
162         assertNull(mPluginInstance.getPlugin());
163         assertInstances(0, 0);
164     }
165 
166     @Test
testOnAttach_SkipLoad()167     public void testOnAttach_SkipLoad() {
168         mPluginListener.mAttachReturn = false;
169         mPluginInstance.onCreate();
170         assertEquals(1, mPluginListener.mAttachedCount);
171         assertEquals(0, mPluginListener.mLoadCount);
172         assertEquals(null, mPluginInstance.getPlugin());
173         assertInstances(0, 0);
174     }
175 
176     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
177     // the mock version info is called.
178     @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
179     public interface TestPlugin extends Plugin {
180         int VERSION = 1;
181         String ACTION = "testAction";
182     }
183 
assertInstances(Integer allocated, Integer created)184     public void assertInstances(Integer allocated, Integer created) {
185         // Run the garbage collector to finalize and deallocate outstanding
186         // instances. Since the GC doesn't always appear to want to run
187         // completely when we ask, we ask it 10 times in a short loop.
188         for (int i = 0; i < 10; i++) {
189             System.runFinalization();
190             System.gc();
191         }
192 
193         mCounter.assertInstances(allocated, created);
194     }
195 
196     public static class RefCounter {
197         public final AtomicInteger mAllocatedInstances = new AtomicInteger();
198         public final AtomicInteger mCreatedInstances = new AtomicInteger();
199 
assertInstances(Integer allocated, Integer created)200         public void assertInstances(Integer allocated, Integer created) {
201             if (allocated != null) {
202                 assertEquals(allocated.intValue(), mAllocatedInstances.get());
203             }
204             if (created != null) {
205                 assertEquals(created.intValue(), mCreatedInstances.get());
206             }
207         }
208     }
209 
210     @Requires(target = TestPlugin.class, version = TestPlugin.VERSION)
211     public static class TestPluginImpl implements TestPlugin {
212         public final RefCounter mCounter;
TestPluginImpl(RefCounter counter)213         public TestPluginImpl(RefCounter counter) {
214             mCounter = counter;
215             mCounter.mAllocatedInstances.getAndIncrement();
216         }
217 
218         @Override
finalize()219         public void finalize() {
220             mCounter.mAllocatedInstances.getAndDecrement();
221         }
222 
223         @Override
onCreate(Context sysuiContext, Context pluginContext)224         public void onCreate(Context sysuiContext, Context pluginContext) {
225             mCounter.mCreatedInstances.getAndIncrement();
226         }
227 
228         @Override
onDestroy()229         public void onDestroy() {
230             mCounter.mCreatedInstances.getAndDecrement();
231         }
232     }
233 
234     public class FakeListener implements PluginListener<TestPlugin> {
235         public boolean mAttachReturn = true;
236         public int mAttachedCount = 0;
237         public int mDetachedCount = 0;
238         public int mLoadCount = 0;
239         public int mUnloadCount = 0;
240 
241         @Override
onPluginAttached(PluginLifecycleManager<TestPlugin> manager)242         public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
243             mAttachedCount++;
244             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
245             return mAttachReturn;
246         }
247 
248         @Override
onPluginDetached(PluginLifecycleManager<TestPlugin> manager)249         public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
250             mDetachedCount++;
251             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
252         }
253 
254         @Override
onPluginLoaded( TestPlugin plugin, Context pluginContext, PluginLifecycleManager<TestPlugin> manager )255         public void onPluginLoaded(
256                 TestPlugin plugin,
257                 Context pluginContext,
258                 PluginLifecycleManager<TestPlugin> manager
259         ) {
260             mLoadCount++;
261             TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get();
262             if (expectedPlugin != null) {
263                 assertEquals(expectedPlugin, plugin);
264             }
265             Context expectedContext = PluginInstanceTest.this.mPluginContext.get();
266             if (expectedContext != null) {
267                 assertEquals(expectedContext, pluginContext);
268             }
269             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
270         }
271 
272         @Override
onPluginUnloaded( TestPlugin plugin, PluginLifecycleManager<TestPlugin> manager )273         public void onPluginUnloaded(
274                 TestPlugin plugin,
275                 PluginLifecycleManager<TestPlugin> manager
276         ) {
277             mUnloadCount++;
278             TestPlugin expectedPlugin = PluginInstanceTest.this.mPlugin.get();
279             if (expectedPlugin != null) {
280                 assertEquals(expectedPlugin, plugin);
281             }
282             assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
283         }
284     }
285 }
286