1 /*
2  * Copyright (C) 2021 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.settings.storage;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.when;
26 
27 import android.app.usage.StorageStats;
28 import android.content.Context;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.UserInfo;
33 import android.os.UserHandle;
34 import android.util.DataUnit;
35 import android.util.SparseArray;
36 
37 import androidx.test.core.app.ApplicationProvider;
38 import androidx.test.ext.junit.runners.AndroidJUnit4;
39 
40 import com.android.car.settings.profiles.ProfileHelper;
41 import com.android.settingslib.applications.StorageStatsSource;
42 
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 import org.mockito.Mock;
47 import org.mockito.MockitoAnnotations;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.List;
53 
54 @RunWith(AndroidJUnit4.class)
55 public class StorageAsyncLoaderTest {
56 
57     private static final int PRIMARY_USER_ID = 0;
58     private static final int SECONDARY_USER_ID = 10;
59     private static final String PACKAGE_NAME_1 = "com.blah.test";
60     private static final String PACKAGE_NAME_2 = "com.blah.test2";
61     private static final long DEFAULT_QUOTA = DataUnit.MEBIBYTES.toBytes(64);
62 
63     private Context mContext = ApplicationProvider.getApplicationContext();
64     private List<UserInfo> mUsers;
65 
66     private StorageAsyncLoader mLoader;
67 
68     @Mock
69     private StorageStatsSource mMockSource;
70     @Mock
71     private PackageManager mMockPackageManager;
72     @Mock
73     private ProfileHelper mMockProfileHelper;
74 
75     @Before
setUp()76     public void setUp() throws Exception {
77         MockitoAnnotations.initMocks(this);
78 
79         mLoader = new StorageAsyncLoader(mContext, mMockSource, mMockPackageManager,
80                 mMockProfileHelper);
81         UserInfo info = new UserInfo();
82         info.id = PRIMARY_USER_ID;
83         mUsers = new ArrayList<>();
84         mUsers.add(info);
85         when(mMockProfileHelper.getAllProfiles()).thenReturn(mUsers);
86         when(mMockSource.getCacheQuotaBytes(any(), anyInt())).thenReturn(DEFAULT_QUOTA);
87     }
88 
89     @Test
testLoadingApps()90     public void testLoadingApps() throws Exception {
91         ApplicationInfo appInfo1 = createAppInfo(PACKAGE_NAME_1, 0, 1, 10,
92                 ApplicationInfo.CATEGORY_UNDEFINED);
93         ApplicationInfo appInfo2 = createAppInfo(PACKAGE_NAME_2, 0, 100, 1000,
94                 ApplicationInfo.CATEGORY_UNDEFINED);
95         when(mMockPackageManager.getInstalledApplicationsAsUser(
96                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
97                 .thenReturn(Arrays.asList(appInfo1, appInfo2));
98 
99         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
100 
101         assertThat(result.size()).isEqualTo(1);
102         assertThat(result.get(PRIMARY_USER_ID).getGamesSize()).isEqualTo(0L);
103         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(2200L);
104     }
105 
106     @Test
testGamesAreFiltered()107     public void testGamesAreFiltered() throws Exception {
108         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 0, 1, 10,
109                 ApplicationInfo.CATEGORY_GAME);
110         when(mMockPackageManager.getInstalledApplicationsAsUser(
111                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
112                 .thenReturn(Collections.singletonList(appInfo));
113 
114         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
115 
116         assertThat(result.size()).isEqualTo(1);
117         assertThat(result.get(PRIMARY_USER_ID).getGamesSize()).isEqualTo(11L);
118         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(0L);
119     }
120 
121     @Test
testLegacyGamesAreFiltered()122     public void testLegacyGamesAreFiltered() throws Exception {
123         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 0, 1, 10,
124                 ApplicationInfo.CATEGORY_UNDEFINED);
125         appInfo.flags = ApplicationInfo.FLAG_IS_GAME;
126         when(mMockPackageManager.getInstalledApplicationsAsUser(
127                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
128                 .thenReturn(Collections.singletonList(appInfo));
129 
130         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
131 
132         assertThat(result.size()).isEqualTo(1);
133         assertThat(result.get(PRIMARY_USER_ID).getGamesSize()).isEqualTo(11L);
134         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(0L);
135     }
136 
137     @Test
testCacheIsNotIgnored()138     public void testCacheIsNotIgnored() throws Exception {
139         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 100, 1, 10,
140                 ApplicationInfo.CATEGORY_UNDEFINED);
141         when(mMockPackageManager.getInstalledApplicationsAsUser(
142                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
143                 .thenReturn(Collections.singletonList(appInfo));
144 
145         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
146 
147         assertThat(result.size()).isEqualTo(1);
148         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(111L);
149     }
150 
151     @Test
testMultipleUsers()152     public void testMultipleUsers() throws Exception {
153         UserInfo info = new UserInfo();
154         info.id = SECONDARY_USER_ID;
155         mUsers.add(info);
156         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 100, 1, 10,
157                 ApplicationInfo.CATEGORY_UNDEFINED);
158         when(mMockPackageManager.getInstalledApplicationsAsUser(
159                 /* getAllInstalledApplications= */ eq(0), anyInt()))
160                 .thenReturn(Collections.singletonList(appInfo));
161         when(mMockSource.getExternalStorageStats(any(), eq(UserHandle.SYSTEM)))
162                 .thenReturn(new StorageStatsSource.ExternalStorageStats(9, 2, 3, 4, 0));
163         when(mMockSource.getExternalStorageStats(any(), eq(new UserHandle(SECONDARY_USER_ID))))
164                 .thenReturn(new StorageStatsSource.ExternalStorageStats(10, 3, 3, 4, 0));
165         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
166 
167         assertThat(result.size()).isEqualTo(2);
168         assertThat(result.get(PRIMARY_USER_ID).getExternalStats().totalBytes).isEqualTo(9L);
169         assertThat(result.get(SECONDARY_USER_ID).getExternalStats().totalBytes).isEqualTo(10L);
170     }
171 
172     @Test
testUpdatedSystemAppCodeSizeIsCounted()173     public void testUpdatedSystemAppCodeSizeIsCounted() throws Exception {
174         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 100, 1, 10,
175                 ApplicationInfo.CATEGORY_UNDEFINED);
176         appInfo.flags = ApplicationInfo.FLAG_SYSTEM & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
177         when(mMockPackageManager.getInstalledApplicationsAsUser(
178                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
179                 .thenReturn(Collections.singletonList(appInfo));
180 
181         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
182 
183         assertThat(result.size()).isEqualTo(1);
184         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(111L);
185     }
186 
187     @Test
testVideoAppsAreFiltered()188     public void testVideoAppsAreFiltered() throws Exception {
189         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 0, 1, 10,
190                 ApplicationInfo.CATEGORY_VIDEO);
191         when(mMockPackageManager.getInstalledApplicationsAsUser(
192                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
193                 .thenReturn(Collections.singletonList(appInfo));
194 
195         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
196 
197         assertThat(result.size()).isEqualTo(1);
198         assertThat(result.get(PRIMARY_USER_ID).getVideoAppsSize()).isEqualTo(11L);
199         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(0L);
200     }
201 
202     @Test
testRemovedPackageDoesNotCrash()203     public void testRemovedPackageDoesNotCrash() throws Exception {
204         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 0, 1, 10,
205                 ApplicationInfo.CATEGORY_UNDEFINED);
206         when(mMockPackageManager.getInstalledApplicationsAsUser(
207                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
208                 .thenReturn(Collections.singletonList(appInfo));
209         when(mMockSource.getStatsForPackage(any(), anyString(), any(UserHandle.class)))
210                 .thenThrow(new PackageManager.NameNotFoundException());
211 
212         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
213 
214         // Should not crash.
215     }
216 
217     @Test
testPackageIsNotDoubleCounted()218     public void testPackageIsNotDoubleCounted() throws Exception {
219         UserInfo info = new UserInfo();
220         info.id = SECONDARY_USER_ID;
221         mUsers.add(info);
222         when(mMockSource.getExternalStorageStats(anyString(), eq(UserHandle.SYSTEM)))
223                 .thenReturn(new StorageStatsSource.ExternalStorageStats(9, 2, 3,
224                         4, 0));
225         when(mMockSource.getExternalStorageStats(anyString(),
226                 eq(new UserHandle(SECONDARY_USER_ID))))
227                 .thenReturn(new StorageStatsSource.ExternalStorageStats(10, 3, 3,
228                         4, 0));
229 
230         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, 0, 1, 10,
231                 ApplicationInfo.CATEGORY_VIDEO);
232         when(mMockPackageManager.getInstalledApplicationsAsUser(
233                 /* getAllInstalledApplications= */ eq(0), anyInt()))
234                 .thenReturn(Collections.singletonList(appInfo));
235 
236         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
237 
238         assertThat(result.size()).isEqualTo(2);
239         assertThat(result.get(PRIMARY_USER_ID).getVideoAppsSize()).isEqualTo(11L);
240         // No code size for the second user.
241         assertThat(result.get(SECONDARY_USER_ID).getVideoAppsSize()).isEqualTo(10L);
242     }
243 
244     @Test
testCacheOveragesAreCountedAsFree()245     public void testCacheOveragesAreCountedAsFree() throws Exception {
246         ApplicationInfo appInfo = createAppInfo(PACKAGE_NAME_1, DEFAULT_QUOTA + 100, 1, 10,
247                 ApplicationInfo.CATEGORY_UNDEFINED);
248 
249         when(mMockPackageManager.getInstalledApplicationsAsUser(
250                 /* getAllInstalledApplications= */ 0, PRIMARY_USER_ID))
251                 .thenReturn(Collections.singletonList(appInfo));
252 
253         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
254 
255         assertThat(result.size()).isEqualTo(1);
256         assertThat(result.get(PRIMARY_USER_ID).getOtherAppsSize()).isEqualTo(DEFAULT_QUOTA + 11);
257     }
258 
createAppInfo(String packageName, long cacheSize, long codeSize, long dataSize, int category)259     private ApplicationInfo createAppInfo(String packageName, long cacheSize, long codeSize,
260             long dataSize, int category) throws Exception {
261         StorageStats stats = new StorageStats();
262         stats.codeBytes = codeSize;
263         stats.dataBytes = dataSize + cacheSize;
264         stats.cacheBytes = cacheSize;
265         StorageStatsSource.AppStorageStats storageStats =
266                 new StorageStatsSource.AppStorageStatsImpl(stats);
267 
268         when(mMockSource.getStatsForPackage(any(), anyString(), any(UserHandle.class)))
269                 .thenReturn(storageStats);
270 
271         ApplicationInfo info = new ApplicationInfo();
272         info.packageName = packageName;
273         info.category = category;
274         PackageInfo packageInfo = new PackageInfo();
275         packageInfo.applicationInfo = info;
276         packageInfo.packageName = packageName;
277 
278         return info;
279     }
280 }
281