1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 package com.android.systemui.shared.plugins; 15 16 import static org.mockito.ArgumentMatchers.anyBoolean; 17 import static org.mockito.Mockito.any; 18 import static org.mockito.Mockito.atLeastOnce; 19 import static org.mockito.Mockito.eq; 20 import static org.mockito.Mockito.mock; 21 import static org.mockito.Mockito.verify; 22 import static org.mockito.Mockito.when; 23 24 import android.app.NotificationManager; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageManager; 30 import android.net.Uri; 31 import android.testing.AndroidTestingRunner; 32 import android.testing.TestableLooper.RunWithLooper; 33 34 import androidx.test.filters.SmallTest; 35 36 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 37 import com.android.systemui.SysuiTestCase; 38 import com.android.systemui.plugins.Plugin; 39 import com.android.systemui.plugins.PluginListener; 40 import com.android.systemui.plugins.annotations.ProvidesInterface; 41 import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager; 42 43 import org.junit.Before; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 import org.mockito.ArgumentCaptor; 47 import org.mockito.Captor; 48 import org.mockito.Mock; 49 import org.mockito.Mockito; 50 import org.mockito.MockitoAnnotations; 51 52 import java.lang.Thread.UncaughtExceptionHandler; 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.List; 56 57 @SmallTest 58 @RunWith(AndroidTestingRunner.class) 59 @RunWithLooper 60 public class PluginManagerTest extends SysuiTestCase { 61 private static final String PRIVILEGED_PACKAGE = "com.android.systemui"; 62 63 @Mock PluginActionManager.Factory mMockFactory; 64 @Mock PluginActionManager<TestPlugin> mMockPluginInstance; 65 @Mock PluginListener<TestPlugin> mMockListener; 66 @Mock PackageManager mMockPackageManager; 67 @Mock PluginEnabler mMockPluginEnabler; 68 @Mock PluginPrefs mMockPluginPrefs; 69 @Mock UncaughtExceptionPreHandlerManager mMockExPreHandlerManager; 70 71 @Captor ArgumentCaptor<UncaughtExceptionHandler> mExPreHandlerCaptor; 72 73 private PluginManagerImpl mPluginManager; 74 private UncaughtExceptionHandler mPluginExceptionHandler; 75 76 @Before setup()77 public void setup() throws Exception { 78 MockitoAnnotations.initMocks(this); 79 when(mMockFactory.create(any(), any(), eq(TestPlugin.class), anyBoolean(), anyBoolean())) 80 .thenReturn(mMockPluginInstance); 81 82 mPluginManager = new PluginManagerImpl( 83 getContext(), mMockFactory, true, 84 mMockExPreHandlerManager, mMockPluginEnabler, 85 mMockPluginPrefs, new ArrayList<>()); 86 captureExceptionHandler(); 87 } 88 89 @Test testAddListener()90 public void testAddListener() { 91 mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); 92 93 verify(mMockPluginInstance).loadAll(); 94 } 95 96 @Test testRemoveListener()97 public void testRemoveListener() { 98 mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); 99 100 mPluginManager.removePluginListener(mMockListener); 101 verify(mMockPluginInstance).destroy(); 102 } 103 104 @Test 105 @RunWithLooper(setAsMainLooper = true) testNonDebuggable_nonPrivileged()106 public void testNonDebuggable_nonPrivileged() { 107 mPluginManager = new PluginManagerImpl( 108 getContext(), mMockFactory, false, 109 mMockExPreHandlerManager, mMockPluginEnabler, 110 mMockPluginPrefs, new ArrayList<>()); 111 captureExceptionHandler(); 112 113 String sourceDir = "myPlugin"; 114 ApplicationInfo applicationInfo = new ApplicationInfo(); 115 applicationInfo.sourceDir = sourceDir; 116 applicationInfo.packageName = PRIVILEGED_PACKAGE; 117 mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); 118 verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class), 119 eq(false), eq(false)); 120 verify(mMockPluginInstance).loadAll(); 121 } 122 123 @Test 124 @RunWithLooper(setAsMainLooper = true) testNonDebuggable_privilegedPackage()125 public void testNonDebuggable_privilegedPackage() { 126 mPluginManager = new PluginManagerImpl( 127 getContext(), mMockFactory, false, 128 mMockExPreHandlerManager, mMockPluginEnabler, 129 mMockPluginPrefs, Collections.singletonList(PRIVILEGED_PACKAGE)); 130 captureExceptionHandler(); 131 132 String sourceDir = "myPlugin"; 133 ApplicationInfo privilegedApplicationInfo = new ApplicationInfo(); 134 privilegedApplicationInfo.sourceDir = sourceDir; 135 privilegedApplicationInfo.packageName = PRIVILEGED_PACKAGE; 136 ApplicationInfo invalidApplicationInfo = new ApplicationInfo(); 137 invalidApplicationInfo.sourceDir = sourceDir; 138 invalidApplicationInfo.packageName = "com.android.invalidpackage"; 139 mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); 140 verify(mMockFactory).create(eq("myAction"), eq(mMockListener), eq(TestPlugin.class), 141 eq(false), eq(false)); 142 verify(mMockPluginInstance).loadAll(); 143 } 144 145 @Test testExceptionHandler_foundPlugin()146 public void testExceptionHandler_foundPlugin() { 147 mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); 148 when(mMockPluginInstance.checkAndDisable(any())).thenReturn(true); 149 150 mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); 151 152 verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable( 153 ArgumentCaptor.forClass(String.class).capture()); 154 verify(mMockPluginInstance, Mockito.never()).disableAll(); 155 } 156 157 @Test testExceptionHandler_noFoundPlugin()158 public void testExceptionHandler_noFoundPlugin() { 159 mPluginManager.addPluginListener("myAction", mMockListener, TestPlugin.class); 160 when(mMockPluginInstance.checkAndDisable(any())).thenReturn(false); 161 162 mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable()); 163 164 verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable( 165 ArgumentCaptor.forClass(String.class).capture()); 166 verify(mMockPluginInstance).disableAll(); 167 } 168 169 @Test testDisableIntent()170 public void testDisableIntent() { 171 NotificationManager nm = mock(NotificationManager.class); 172 mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, nm); 173 mContext.setMockPackageManager(mMockPackageManager); 174 175 ComponentName testComponent = new ComponentName(getContext().getPackageName(), 176 PluginManagerTest.class.getName()); 177 Intent intent = new Intent(PluginManagerImpl.DISABLE_PLUGIN); 178 intent.setData(Uri.parse("package://" + testComponent.flattenToString())); 179 mPluginManager.onReceive(mContext, intent); 180 verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN)); 181 verify(mMockPluginEnabler).setDisabled(testComponent, 182 PluginEnabler.DISABLED_INVALID_VERSION); 183 } 184 captureExceptionHandler()185 private void captureExceptionHandler() { 186 verify(mMockExPreHandlerManager, atLeastOnce()).registerHandler( 187 mExPreHandlerCaptor.capture()); 188 List<UncaughtExceptionHandler> allValues = mExPreHandlerCaptor.getAllValues(); 189 mPluginExceptionHandler = allValues.get(allValues.size() - 1); 190 } 191 192 @ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION) 193 public interface TestPlugin extends Plugin { 194 String ACTION = "testAction"; 195 int VERSION = 1; 196 } 197 } 198