• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.server;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Matchers.anyInt;
22 import static org.mockito.Mockito.doReturn;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.spy;
25 import static org.mockito.Mockito.when;
26 
27 import android.app.ActivityManagerInternal;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.res.Resources;
32 import android.os.Binder;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.testing.AndroidTestingRunner;
36 import android.testing.TestableContext;
37 import android.testing.TestableLooper;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 
41 import androidx.test.InstrumentationRegistry;
42 
43 import com.android.server.wm.ActivityTaskManagerInternal;
44 
45 import org.junit.After;
46 import org.junit.Before;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.mockito.Mockito;
51 import org.mockito.MockitoAnnotations;
52 
53 import java.io.BufferedReader;
54 import java.io.CharArrayWriter;
55 import java.io.FileDescriptor;
56 import java.io.PrintWriter;
57 import java.io.StringReader;
58 import java.lang.reflect.Constructor;
59 import java.lang.reflect.Field;
60 import java.lang.reflect.Method;
61 import java.util.Optional;
62 import java.util.concurrent.TimeUnit;
63 
64 @RunWith(AndroidTestingRunner.class)
65 @TestableLooper.RunWithLooper
66 public class PinnerServiceTest {
67     private static final int KEY_CAMERA = 0;
68     private static final int KEY_HOME = 1;
69     private static final int KEY_ASSISTANT = 2;
70 
71     private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
72 
73     @Rule
74     public TestableContext mContext =
75             new TestableContext(InstrumentationRegistry.getContext(), null);
76 
77     private final ArraySet<String> mUpdatedPackages = new ArraySet<>();
78     private ResolveInfo mHomePackageResolveInfo;
79 
80     @Before
setUp()81     public void setUp() {
82         MockitoAnnotations.initMocks(this);
83 
84         if (Looper.myLooper() == null) {
85             Looper.prepare();
86         }
87 
88         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
89         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
90 
91         ActivityTaskManagerInternal mockActivityTaskManagerInternal = mock(
92                 ActivityTaskManagerInternal.class);
93         Intent homeIntent = getHomeIntent();
94 
95         doReturn(homeIntent).when(mockActivityTaskManagerInternal).getHomeIntent();
96         LocalServices.addService(ActivityTaskManagerInternal.class,
97                 mockActivityTaskManagerInternal);
98 
99         ActivityManagerInternal mockActivityManagerInternal = mock(ActivityManagerInternal.class);
100         doReturn(true).when(mockActivityManagerInternal).isUidActive(anyInt());
101         LocalServices.addService(ActivityManagerInternal.class, mockActivityManagerInternal);
102 
103         mContext = spy(mContext);
104 
105         // Get HOME (Launcher) package
106         mHomePackageResolveInfo = mContext.getPackageManager().resolveActivityAsUser(homeIntent,
107                 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
108                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
109         mUpdatedPackages.add(mHomePackageResolveInfo.activityInfo.applicationInfo.packageName);
110     }
111 
112     @After
tearDown()113     public void tearDown() {
114         Mockito.framework().clearInlineMocks();
115     }
116 
getHomeIntent()117     private Intent getHomeIntent() {
118         Intent intent = new Intent(Intent.ACTION_MAIN);
119         intent.addCategory(Intent.CATEGORY_HOME);
120         intent.addCategory(Intent.CATEGORY_DEFAULT);
121         return intent;
122     }
123 
unpinAll(PinnerService pinnerService)124     private void unpinAll(PinnerService pinnerService) throws Exception {
125         // unpin all packages
126         Method unpinAppMethod = PinnerService.class.getDeclaredMethod("unpinApp", int.class);
127         unpinAppMethod.setAccessible(true);
128         unpinAppMethod.invoke(pinnerService, KEY_HOME);
129         unpinAppMethod.invoke(pinnerService, KEY_CAMERA);
130         unpinAppMethod.invoke(pinnerService, KEY_ASSISTANT);
131     }
132 
waitForPinnerService(PinnerService pinnerService)133     private void waitForPinnerService(PinnerService pinnerService)
134             throws NoSuchFieldException, IllegalAccessException {
135         // There's no notification/callback when pinning finished
136         // Block until pinner handler is done pinning and runs this empty runnable
137         Field pinnerHandlerField = PinnerService.class.getDeclaredField("mPinnerHandler");
138         pinnerHandlerField.setAccessible(true);
139         Handler pinnerServiceHandler = (Handler) pinnerHandlerField.get(pinnerService);
140         pinnerServiceHandler.runWithScissors(() -> {
141         }, WAIT_FOR_PINNER_TIMEOUT);
142     }
143 
getPinKeys(PinnerService pinnerService)144     private ArraySet<Integer> getPinKeys(PinnerService pinnerService)
145             throws NoSuchFieldException, IllegalAccessException {
146         Field pinKeysArrayField = PinnerService.class.getDeclaredField("mPinKeys");
147         pinKeysArrayField.setAccessible(true);
148         return (ArraySet<Integer>) pinKeysArrayField.get(pinnerService);
149     }
150 
getPinnedApps(PinnerService pinnerService)151     private ArrayMap<Integer, Object> getPinnedApps(PinnerService pinnerService)
152             throws NoSuchFieldException, IllegalAccessException {
153         Field pinnedAppsField = PinnerService.class.getDeclaredField("mPinnedApps");
154         pinnedAppsField.setAccessible(true);
155         return (ArrayMap<Integer, Object>) pinnedAppsField.get(
156                 pinnerService);
157     }
158 
getPinnerServiceDump(PinnerService pinnerService)159     private String getPinnerServiceDump(PinnerService pinnerService) throws Exception {
160         Class<?> innerClass = Class.forName(PinnerService.class.getName() + "$BinderService");
161         Constructor<?> ctor = innerClass.getDeclaredConstructor(PinnerService.class);
162         ctor.setAccessible(true);
163         Binder innerInstance = (Binder) ctor.newInstance(pinnerService);
164         CharArrayWriter cw = new CharArrayWriter();
165         PrintWriter pw = new PrintWriter(cw, true);
166         Method dumpMethod = Binder.class.getDeclaredMethod("dump", FileDescriptor.class,
167                 PrintWriter.class, String[].class);
168         dumpMethod.setAccessible(true);
169         dumpMethod.invoke(innerInstance, null, pw, null);
170         return cw.toString();
171     }
172 
getPinnedSize(PinnerService pinnerService)173     private int getPinnedSize(PinnerService pinnerService) throws Exception {
174         final String totalSizeToken = "Total size: ";
175         String dumpOutput = getPinnerServiceDump(pinnerService);
176         BufferedReader bufReader = new BufferedReader(new StringReader(dumpOutput));
177         Optional<Integer> size = bufReader.lines().filter(s -> s.contains(totalSizeToken))
178                 .map(s -> Integer.valueOf(s.substring(totalSizeToken.length()))).findAny();
179         return size.orElse(-1);
180     }
181 
182     @Test
testPinHomeApp()183     public void testPinHomeApp() throws Exception {
184         // Enable HOME app pinning
185         Resources res = mock(Resources.class);
186         doReturn(true).when(res).getBoolean(com.android.internal.R.bool.config_pinnerHomeApp);
187         when(mContext.getResources()).thenReturn(res);
188         PinnerService pinnerService = new PinnerService(mContext);
189 
190         ArraySet<Integer> pinKeys = getPinKeys(pinnerService);
191         assertThat(pinKeys.valueAt(0)).isEqualTo(KEY_HOME);
192 
193         pinnerService.update(mUpdatedPackages, true);
194 
195         waitForPinnerService(pinnerService);
196 
197         ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
198         assertThat(pinnedApps.get(KEY_HOME)).isNotNull();
199 
200         // Check if dump() reports total pinned bytes
201         int totalPinnedSizeBytes = getPinnedSize(pinnerService);
202         assertThat(totalPinnedSizeBytes).isGreaterThan(0);
203 
204         // Make sure pinned files are unmapped
205         unpinAll(pinnerService);
206     }
207 
208     @Test
testPinHomeAppOnBootCompleted()209     public void testPinHomeAppOnBootCompleted() throws Exception {
210         // Enable HOME app pinning
211         Resources res = mock(Resources.class);
212         doReturn(true).when(res).getBoolean(com.android.internal.R.bool.config_pinnerHomeApp);
213         when(mContext.getResources()).thenReturn(res);
214         PinnerService pinnerService = new PinnerService(mContext);
215 
216         ArraySet<Integer> pinKeys = getPinKeys(pinnerService);
217         assertThat(pinKeys.valueAt(0)).isEqualTo(KEY_HOME);
218 
219         pinnerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
220 
221         waitForPinnerService(pinnerService);
222 
223         ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
224         assertThat(pinnedApps.get(KEY_HOME)).isNotNull();
225 
226         // Check if dump() reports total pinned bytes
227         int totalPinnedSizeBytes = getPinnedSize(pinnerService);
228         assertThat(totalPinnedSizeBytes).isGreaterThan(0);
229 
230         // Make sure pinned files are unmapped
231         unpinAll(pinnerService);
232     }
233 
234     @Test
testNothingToPin()235     public void testNothingToPin() throws Exception {
236         // No package enabled for pinning
237         Resources res = mock(Resources.class);
238         when(mContext.getResources()).thenReturn(res);
239         PinnerService pinnerService = new PinnerService(mContext);
240 
241         ArraySet<Integer> pinKeys = getPinKeys(pinnerService);
242         assertThat(pinKeys).isEmpty();
243 
244         pinnerService.update(mUpdatedPackages, true);
245 
246         waitForPinnerService(pinnerService);
247 
248         ArrayMap<Integer, Object> pinnedApps = getPinnedApps(pinnerService);
249         assertThat(pinnedApps).isEmpty();
250 
251         // Check if dump() reports total pinned bytes
252         int totalPinnedSizeBytes = getPinnedSize(pinnerService);
253         assertThat(totalPinnedSizeBytes).isEqualTo(0);
254 
255         // Make sure pinned files are unmapped
256         unpinAll(pinnerService);
257     }
258 
259     // TODO: Add test to check that the pages we expect to be pinned are actually pinned
260 
261 }
262