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