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