1 /*
2  * Copyright (C) 2016 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.assertFalse;
20 import static junit.framework.Assert.assertTrue;
21 
22 import static org.mockito.Matchers.any;
23 import static org.mockito.Matchers.anyInt;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.never;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.app.Activity;
30 import android.app.NotificationManager;
31 import android.content.BroadcastReceiver;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.pm.ServiceInfo;
40 
41 import androidx.test.filters.SmallTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import com.android.systemui.SysuiTestCase;
45 import com.android.systemui.SysuiTestableContext;
46 import com.android.systemui.plugins.Plugin;
47 import com.android.systemui.plugins.PluginListener;
48 import com.android.systemui.plugins.annotations.Requires;
49 import com.android.systemui.util.concurrency.FakeExecutor;
50 import com.android.systemui.util.time.FakeSystemClock;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.ArgumentCaptor;
56 import org.mockito.Mockito;
57 import org.mockito.stubbing.Answer;
58 
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.List;
62 
63 @SmallTest
64 @RunWith(AndroidJUnit4.class)
65 public class PluginActionManagerTest extends SysuiTestCase {
66 
67     private static final String PRIVILEGED_PACKAGE = "com.android.systemui.shared.plugins";
68     private TestPlugin mMockPlugin;
69 
70     private PackageManager mMockPm;
71     private PluginListener<TestPlugin> mMockListener;
72     private PluginActionManager<TestPlugin> mPluginActionManager;
73     private VersionInfo mMockVersionInfo;
74     private PluginEnabler mMockEnabler;
75     ComponentName mTestPluginComponentName =
76             new ComponentName(PRIVILEGED_PACKAGE, TestPlugin.class.getName());
77     private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
78     NotificationManager mNotificationManager;
79     private PluginInstance<TestPlugin> mPluginInstance;
80     private PluginInstance.Factory mPluginInstanceFactory = new PluginInstance.Factory(
81             this.getClass().getClassLoader(),
82             new PluginInstance.InstanceFactory<>(), new PluginInstance.VersionCheckerImpl(),
83             Collections.emptyList(), false) {
84         @Override
85         public <T extends Plugin> PluginInstance<T> create(Context context, ApplicationInfo appInfo,
86                 ComponentName componentName, Class<T> pluginClass, PluginListener<T> listener) {
87             return (PluginInstance<T>) mPluginInstance;
88         }
89     };
90 
91     private PluginActionManager.Factory mActionManagerFactory;
92 
93     @Before
setup()94     public void setup() throws Exception {
95         mContext = new MyContextWrapper(mContext);
96         mMockPm = mock(PackageManager.class);
97         mMockListener = mock(PluginListener.class);
98         mMockEnabler = mock(PluginEnabler.class);
99         mMockVersionInfo = mock(VersionInfo.class);
100         mNotificationManager = mock(NotificationManager.class);
101         mMockPlugin = mock(TestPlugin.class);
102         mPluginInstance = mock(PluginInstance.class);
103         when(mPluginInstance.getComponentName()).thenReturn(mTestPluginComponentName);
104         when(mPluginInstance.getPackage()).thenReturn(mTestPluginComponentName.getPackageName());
105         mActionManagerFactory = new PluginActionManager.Factory(getContext(), mMockPm,
106                 mFakeExecutor, mFakeExecutor, mNotificationManager, mMockEnabler,
107                 new ArrayList<>(), mPluginInstanceFactory);
108 
109         mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener,
110                 TestPlugin.class, true, true);
111         when(mMockPlugin.getVersion()).thenReturn(1);
112     }
113 
114     @Test
testNoPlugins()115     public void testNoPlugins() {
116         when(mMockPm.queryIntentServices(any(), anyInt())).thenReturn(
117                 Collections.emptyList());
118         mPluginActionManager.loadAll();
119 
120         mFakeExecutor.runAllReady();
121 
122         verify(mMockListener, never()).onPluginConnected(any(), any());
123     }
124 
125     @Test
testPluginCreate()126     public void testPluginCreate() throws Exception {
127         //Debug.waitForDebugger();
128         createPlugin();
129 
130         // Verify startup lifecycle
131         verify(mPluginInstance).onCreate();
132     }
133 
134     @Test
testPluginDestroy()135     public void testPluginDestroy() throws Exception {
136         createPlugin(); // Get into valid created state.
137 
138         mPluginActionManager.destroy();
139 
140         mFakeExecutor.runAllReady();
141 
142         // Verify shutdown lifecycle
143         verify(mPluginInstance).onDestroy();
144     }
145 
146     @Test
testReloadOnChange()147     public void testReloadOnChange() throws Exception {
148         createPlugin(); // Get into valid created state.
149 
150         mPluginActionManager.reloadPackage(PRIVILEGED_PACKAGE);
151 
152         mFakeExecutor.runAllReady();
153 
154         // Verify the old one was destroyed.
155         verify(mPluginInstance).onDestroy();
156         verify(mPluginInstance, Mockito.times(2))
157                 .onCreate();
158     }
159 
160     @Test
testNonDebuggable()161     public void testNonDebuggable() throws Exception {
162         // Create a version that thinks the build is not debuggable.
163         mPluginActionManager = mActionManagerFactory.create("myAction", mMockListener,
164                 TestPlugin.class, true, false);
165         setupFakePmQuery();
166 
167         mPluginActionManager.loadAll();
168 
169         mFakeExecutor.runAllReady();
170 
171         // Non-debuggable build should receive no plugins.
172         verify(mMockListener, never()).onPluginConnected(any(), any());
173     }
174 
175     @Test
testNonDebuggable_privileged()176     public void testNonDebuggable_privileged() throws Exception {
177         // Create a version that thinks the build is not debuggable.
178         PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(),
179                 mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager,
180                 mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE),
181                 mPluginInstanceFactory);
182         mPluginActionManager = factory.create("myAction", mMockListener,
183                 TestPlugin.class, true, false);
184         setupFakePmQuery();
185 
186         mPluginActionManager.loadAll();
187 
188         mFakeExecutor.runAllReady();
189 
190         // Verify startup lifecycle
191         verify(mPluginInstance).onCreate();
192     }
193 
194     @Test
testCheckAndDisable()195     public void testCheckAndDisable() throws Exception {
196         createPlugin(); // Get into valid created state.
197 
198         // Start with an unrelated class.
199         boolean result = mPluginActionManager.checkAndDisable(Activity.class.getName());
200         assertFalse(result);
201         verify(mMockEnabler, never()).setDisabled(any(ComponentName.class), anyInt());
202 
203         // Now hand it a real class and make sure it disables the plugin.
204         result = mPluginActionManager.checkAndDisable(TestPlugin.class.getName());
205         assertTrue(result);
206         verify(mMockEnabler).setDisabled(
207                 mTestPluginComponentName, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH);
208     }
209 
210     @Test
testDisableAll()211     public void testDisableAll() throws Exception {
212         createPlugin(); // Get into valid created state.
213 
214         mPluginActionManager.disableAll();
215 
216         verify(mMockEnabler).setDisabled(
217                 mTestPluginComponentName, PluginEnabler.DISABLED_FROM_SYSTEM_CRASH);
218     }
219 
220     @Test
testDisablePrivileged()221     public void testDisablePrivileged() throws Exception {
222         PluginActionManager.Factory factory = new PluginActionManager.Factory(getContext(),
223                 mMockPm, mFakeExecutor, mFakeExecutor, mNotificationManager,
224                 mMockEnabler, Collections.singletonList(PRIVILEGED_PACKAGE),
225                 mPluginInstanceFactory);
226         mPluginActionManager = factory.create("myAction", mMockListener,
227                 TestPlugin.class, true, false);
228 
229         createPlugin(); // Get into valid created state.
230 
231         mPluginActionManager.disableAll();
232 
233         verify(mMockPm, never()).setComponentEnabledSetting(
234                 ArgumentCaptor.forClass(ComponentName.class).capture(),
235                 ArgumentCaptor.forClass(int.class).capture(),
236                 ArgumentCaptor.forClass(int.class).capture());
237     }
238 
setupFakePmQuery()239     private void setupFakePmQuery() throws Exception {
240         List<ResolveInfo> list = new ArrayList<>();
241         ResolveInfo info = new ResolveInfo();
242         info.serviceInfo = mock(ServiceInfo.class);
243         info.serviceInfo.packageName = mTestPluginComponentName.getPackageName();
244         info.serviceInfo.name = mTestPluginComponentName.getClassName();
245         when(info.serviceInfo.loadLabel(any())).thenReturn("Test Plugin");
246         list.add(info);
247         when(mMockPm.queryIntentServices(any(), Mockito.anyInt())).thenReturn(list);
248         when(mMockPm.getServiceInfo(any(), anyInt())).thenReturn(info.serviceInfo);
249 
250         when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
251                 PackageManager.PERMISSION_GRANTED);
252 
253         when(mMockPm.getApplicationInfo(Mockito.anyString(), anyInt())).thenAnswer(
254                 (Answer<ApplicationInfo>) invocation -> {
255                     ApplicationInfo appInfo = getContext().getApplicationInfo();
256                     appInfo.packageName = invocation.getArgument(0);
257                     return appInfo;
258                 });
259         when(mMockEnabler.isEnabled(mTestPluginComponentName)).thenReturn(true);
260     }
261 
createPlugin()262     private void createPlugin() throws Exception {
263         setupFakePmQuery();
264 
265         mPluginActionManager.loadAll();
266 
267         mFakeExecutor.runAllReady();
268     }
269 
270     // Real context with no registering/unregistering of receivers.
271     private static class MyContextWrapper extends SysuiTestableContext {
MyContextWrapper(Context base)272         MyContextWrapper(Context base) {
273             super(base);
274         }
275 
276         @Override
registerReceiver(BroadcastReceiver receiver, IntentFilter filter)277         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
278             return null;
279         }
280 
281         @Override
unregisterReceiver(BroadcastReceiver receiver)282         public void unregisterReceiver(BroadcastReceiver receiver) {
283         }
284 
285         @Override
sendBroadcast(Intent intent)286         public void sendBroadcast(Intent intent) {
287             // Do nothing.
288         }
289     }
290 
291     // This target class doesn't matter, it just needs to have a Requires to hit the flow where
292     // the mock version info is called.
293     @Requires(target = PluginManagerTest.class, version = 1)
294     public static class TestPlugin implements Plugin {
295         @Override
getVersion()296         public int getVersion() {
297             return 1;
298         }
299 
300         @Override
onCreate(Context sysuiContext, Context pluginContext)301         public void onCreate(Context sysuiContext, Context pluginContext) {
302         }
303 
304         @Override
onDestroy()305         public void onDestroy() {
306         }
307     }
308 }
309