1 /*
2  * Copyright (C) 2022 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.car.telemetry;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.ArgumentMatchers.isNull;
25 import static org.mockito.Mockito.doAnswer;
26 import static org.mockito.Mockito.when;
27 
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.net.Uri;
36 import android.os.Looper;
37 import android.os.UserHandle;
38 import android.os.UserManager;
39 
40 import com.android.car.test.FakeHandlerWrapper;
41 
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.Mock;
46 import org.mockito.invocation.InvocationOnMock;
47 import org.mockito.junit.MockitoJUnitRunner;
48 
49 import java.util.ArrayList;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.concurrent.atomic.AtomicReference;
53 
54 @RunWith(MockitoJUnitRunner.class)
55 public class UidPackageMapperTest {
56     private static final int PACKAGE_ALL_FLAGS =
57             PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_ANY_USER;
58 
59     private static final int MAX_REMOVED_APPS_COUNT = 1;
60 
61     // List of PackageInfo for uid/packageName.
62     private static final PackageInfo sPackageA = buildPackageInfo(1, "com.android.a", false);
63     private static final PackageInfo sPackageB = buildPackageInfo(1, "com.android.b", false);
64     private static final PackageInfo sPackageC = buildPackageInfo(2, "com.android.c", false);
65     private static final PackageInfo sPackageApex = buildPackageInfo(4, "com.andr.apex", true);
66     private static final PackageInfo sPackageN = buildPackageInfo(5, "com.android.n", false);
67     private static final PackageInfo sPackageX = buildPackageInfo(10, "com.android.x", false);
68 
69     private static final UserHandle sUserHandle1 = new UserHandle(/* userId= */ 1);
70     private static final UserHandle sUserHandle2New = new UserHandle(/* userId= */ 2);
71 
72     @Mock private PackageManager mMockPackageManager;
73     @Mock private UserManager mMockUserManager;
74     @Mock private Context mMockContext;
75 
76     private final AtomicReference<BroadcastReceiver> mUserUpdateReceiver = new AtomicReference<>();
77     private final AtomicReference<BroadcastReceiver> mAppUpdateReceiver = new AtomicReference<>();
78 
79     private FakeHandlerWrapper mDirectHandler =
80             new FakeHandlerWrapper(Looper.getMainLooper(), FakeHandlerWrapper.Mode.IMMEDIATE);
81     private UidPackageMapper mUidMapper; // subject
82 
83     @Before
setUp()84     public void setUp() {
85         when(mMockUserManager.getUserHandles(anyBoolean())).thenReturn(List.of(sUserHandle1));
86 
87         // sUserHandle1 packages
88         when(mMockPackageManager.getInstalledPackagesAsUser(
89                         eq(PACKAGE_ALL_FLAGS), eq(sUserHandle1.getIdentifier())))
90                 .thenReturn(List.of(sPackageA, sPackageB, sPackageC));
91         // sUserHandle2New packages
92         when(mMockPackageManager.getInstalledPackagesAsUser(
93                         eq(PACKAGE_ALL_FLAGS), eq(sUserHandle2New.getIdentifier())))
94                 .thenReturn(List.of(sPackageX));
95         // Apex packages
96         when(mMockPackageManager.getInstalledPackages(eq(PackageManager.MATCH_APEX)))
97                 .thenReturn(List.of(sPackageApex));
98 
99         when(mMockContext.getSystemService(UserManager.class)).thenReturn(mMockUserManager);
100         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
101 
102         doAnswer(this::onRegisterReceiverForAllUsers)
103                 .when(mMockContext)
104                 .registerReceiverForAllUsers(any(), any(), isNull(), isNull());
105 
106         mUidMapper =
107                 new UidPackageMapper(
108                         mMockContext, mDirectHandler.getMockHandler(), MAX_REMOVED_APPS_COUNT);
109     }
110 
onRegisterReceiverForAllUsers(InvocationOnMock invocation)111     private Intent onRegisterReceiverForAllUsers(InvocationOnMock invocation) {
112         IntentFilter filter = invocation.getArgument(1);
113         if (toList(filter.actionsIterator()).contains(Intent.ACTION_PACKAGE_ADDED)) {
114             mAppUpdateReceiver.set(invocation.getArgument(0));
115         }
116         if (toList(filter.actionsIterator()).contains(Intent.ACTION_USER_INITIALIZE)) {
117             mUserUpdateReceiver.set(invocation.getArgument(0));
118         }
119         return null; // should return Intent, but the result is not used.
120     }
121 
122     @Test
testInitPullsAllPackages()123     public void testInitPullsAllPackages() {
124         mUidMapper.init();
125 
126         assertThat(mUidMapper.getPackagesForUid(1))
127                 .containsExactly("com.android.a", "com.android.b");
128         assertThat(mUidMapper.getPackagesForUid(2)).containsExactly("com.android.c");
129         assertThat(mUidMapper.getPackagesForUid(4)).containsExactly("com.andr.apex");
130         assertThat(mUidMapper.getPackagesForUid(5)).isEmpty();
131         assertThat(mUidMapper.getPackagesForUid(10)).isEmpty();
132     }
133 
134     @Test
testOnAppInstalled()135     public void testOnAppInstalled() {
136         mUidMapper.init();
137 
138         broadcastAppAdded(sPackageN); // uid = 5
139 
140         assertThat(mUidMapper.getPackagesForUid(5)).containsExactly("com.android.n");
141         assertThat(mUidMapper.getPackagesForUid(1)) // other apps are there too
142                 .containsExactly("com.android.a", "com.android.b");
143     }
144 
145     @Test
testOnAppRemoved()146     public void testOnAppRemoved() {
147         mUidMapper.init();
148 
149         broadcastAppRemoved(sPackageB);
150 
151         broadcastAppRemoved(sPackageC); // To remove B from the cache, remove C too.
152 
153         // "com.android.b" is removed, and deleted from UidPackageMapper cache too,
154         // because MAX_REMOVED_APPS_COUNT = 1.
155         assertThat(mUidMapper.getPackagesForUid(1)).containsExactly("com.android.a");
156         // "com.android.c" is still cached in UidPackageMapper, even if the app is removed.
157         assertThat(mUidMapper.getPackagesForUid(2)).containsExactly("com.android.c");
158     }
159 
160     // Tests if cached uninstalled apps are handled well, when installed back again.
161     @Test
testOnAppRemovedThenInstalled()162     public void testOnAppRemovedThenInstalled() {
163         mUidMapper.init();
164 
165         broadcastAppRemoved(sPackageB);
166 
167         broadcastAppAdded(sPackageB);
168 
169         // Remove "sPackageC" to trigger cache cleanup, it should try to remove "sPackageB",
170         // but mustn't, as B is installed again.
171         broadcastAppRemoved(sPackageC);
172 
173         // Remove "sPackageApex" to trigger to cleanup "sPackageC".
174         broadcastAppRemoved(sPackageApex);
175 
176         assertThat(mUidMapper.getPackagesForUid(1))
177                 .containsExactly("com.android.a", "com.android.b");
178         assertThat(mUidMapper.getPackagesForUid(2)).isEmpty(); // sPackageC is cleaned-up from cache
179     }
180 
181     @Test
testOnUserAdded()182     public void testOnUserAdded() {
183         mUidMapper.init();
184         when(mMockUserManager.getUserHandles(anyBoolean()))
185                 .thenReturn(List.of(sUserHandle1, sUserHandle2New));
186 
187         Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
188         mUserUpdateReceiver.get().onReceive(mMockContext, intent);
189 
190         assertThat(mUidMapper.getPackagesForUid(1))
191                 .containsExactly("com.android.a", "com.android.b");
192         // "com.android.x" already exists in sUserHandle2New.
193         assertThat(mUidMapper.getPackagesForUid(10)).containsExactly("com.android.x");
194     }
195 
broadcastAppAdded(PackageInfo packageInfo)196     private void broadcastAppAdded(PackageInfo packageInfo) {
197         Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);
198         intent.setData(Uri.fromParts("package", packageInfo.packageName, null));
199         intent.putExtra(Intent.EXTRA_UID, packageInfo.applicationInfo.uid);
200         mAppUpdateReceiver.get().onReceive(mMockContext, intent);
201     }
202 
broadcastAppRemoved(PackageInfo packageInfo)203     private void broadcastAppRemoved(PackageInfo packageInfo) {
204         Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
205         intent.setData(Uri.fromParts("package", packageInfo.packageName, null));
206         intent.putExtra(Intent.EXTRA_UID, packageInfo.applicationInfo.uid);
207         mAppUpdateReceiver.get().onReceive(mMockContext, intent);
208     }
209 
210     /** Converts iterator to a list. */
toList(Iterator<T> iterator)211     private static <T> List<T> toList(Iterator<T> iterator) {
212         ArrayList<T> result = new ArrayList<>();
213         iterator.forEachRemaining(result::add);
214         return result;
215     }
216 
buildPackageInfo(int uid, String packageName, boolean isApex)217     private static PackageInfo buildPackageInfo(int uid, String packageName, boolean isApex) {
218         PackageInfo info = new PackageInfo();
219         info.packageName = packageName;
220         info.applicationInfo = new ApplicationInfo();
221         info.applicationInfo.uid = uid;
222         info.isApex = isApex;
223         return info;
224     }
225 }
226