1/*
2 * Copyright (C) 2016 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
17package com.android.server.storage;
18
19import static com.google.common.truth.Truth.assertThat;
20
21import static org.mockito.Matchers.any;
22import static org.mockito.Matchers.anyInt;
23import static org.mockito.Matchers.anyLong;
24import static org.mockito.Matchers.anyString;
25import static org.mockito.Matchers.isNull;
26import static org.mockito.Mockito.doReturn;
27import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.spy;
29import static org.mockito.Mockito.when;
30
31import android.app.job.JobService;
32import android.app.job.JobServiceEngine;
33import android.app.usage.ExternalStorageStats;
34import android.app.usage.StorageStatsManager;
35import android.content.Context;
36import android.content.pm.PackageManager;
37import android.content.pm.PackageStats;
38import android.os.BatteryManager;
39import android.os.UserHandle;
40import android.os.UserManager;
41import android.os.storage.StorageManager;
42import android.os.storage.VolumeInfo;
43import android.provider.Settings;
44import android.test.AndroidTestCase;
45import android.test.mock.MockContentResolver;
46
47import com.android.internal.util.test.FakeSettingsProvider;
48import com.android.server.storage.DiskStatsLoggingService.LogRunnable;
49
50import libcore.io.IoUtils;
51
52import org.json.JSONObject;
53import org.junit.Before;
54import org.junit.Rule;
55import org.junit.Test;
56import org.junit.rules.TemporaryFolder;
57import org.junit.runner.RunWith;
58import org.junit.runners.JUnit4;
59import org.mockito.Mock;
60import org.mockito.MockitoAnnotations;
61
62import java.io.File;
63import java.io.PrintStream;
64import java.lang.reflect.Field;
65import java.util.ArrayList;
66
67@RunWith(JUnit4.class)
68public class DiskStatsLoggingServiceTest extends AndroidTestCase {
69    @Rule public TemporaryFolder mTemporaryFolder;
70    @Rule public TemporaryFolder mDownloads;
71    @Mock private AppCollector mCollector;
72    @Mock private JobService mJobService;
73    @Mock private StorageStatsManager mSsm;
74    private ExternalStorageStats mStorageStats;
75    private File mInputFile;
76
77
78    @Before
79    public void setUp() throws Exception {
80        super.setUp();
81        MockitoAnnotations.initMocks(this);
82        mTemporaryFolder = new TemporaryFolder();
83        mTemporaryFolder.create();
84        mInputFile = mTemporaryFolder.newFile();
85        mDownloads = new TemporaryFolder();
86        mDownloads.create();
87        mStorageStats = new ExternalStorageStats();
88        when(mSsm.queryExternalStatsForUser(isNull(String.class), any(UserHandle.class)))
89                .thenReturn(mStorageStats);
90        when(mJobService.getSystemService(anyString())).thenReturn(mSsm);
91    }
92
93    @Test
94    public void testEmptyLog() throws Exception {
95        LogRunnable task = new LogRunnable();
96        task.setAppCollector(mCollector);
97        task.setDownloadsDirectory(mDownloads.getRoot());
98        task.setLogOutputFile(mInputFile);
99        task.setSystemSize(0L);
100        task.setContext(mJobService);
101        task.run();
102
103        JSONObject json = getJsonOutput();
104        assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L);
105        assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L);
106        assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L);
107        assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L);
108        assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L);
109        assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L);
110        assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L);
111        assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L);
112        assertThat(
113                json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L);
114        assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L);
115        assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L);
116    }
117
118    @Test
119    public void testPopulatedLogTask() throws Exception {
120        // Write data to directories.
121        writeDataToFile(mDownloads.newFile(), "lol");
122        mStorageStats.audioBytes = 6L;
123        mStorageStats.imageBytes = 4L;
124        mStorageStats.videoBytes = 5L;
125        mStorageStats.totalBytes = 22L;
126
127        // Write apps.
128        ArrayList<PackageStats> apps = new ArrayList<>();
129        PackageStats testApp = new PackageStats("com.test.app");
130        testApp.dataSize = 5L;
131        testApp.cacheSize = 55L;
132        testApp.codeSize = 10L;
133        testApp.userHandle = UserHandle.USER_SYSTEM;
134        apps.add(testApp);
135        when(mCollector.getPackageStats(anyLong())).thenReturn(apps);
136
137        LogRunnable task = new LogRunnable();
138        task.setAppCollector(mCollector);
139        task.setDownloadsDirectory(mDownloads.getRoot());
140        task.setLogOutputFile(mInputFile);
141        task.setSystemSize(10L);
142        task.setContext(mJobService);
143        task.run();
144
145        JSONObject json = getJsonOutput();
146        assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(4L);
147        assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(5L);
148        assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(6L);
149        assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(3L);
150        assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(10L);
151        assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(7L);
152        assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(10L);
153        assertThat(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY)).isEqualTo(5L);
154        assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(55L);
155        assertThat(
156                json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(1L);
157        assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(1L);
158        assertThat(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY).length()).isEqualTo(1L);
159        assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(1L);
160    }
161
162    @Test
163    public void testDontCrashOnPackageStatsTimeout() throws Exception {
164        when(mCollector.getPackageStats(anyInt())).thenReturn(null);
165
166        LogRunnable task = new LogRunnable();
167        task.setAppCollector(mCollector);
168        task.setDownloadsDirectory(mDownloads.getRoot());
169        task.setLogOutputFile(mInputFile);
170        task.setSystemSize(10L);
171        task.setContext(mJobService);
172        task.run();
173
174        // No exception should be thrown.
175    }
176
177    @Test
178    public void testDontCrashOnRun() throws Exception {
179        DiskStatsLoggingService service = spy(new DiskStatsLoggingService());
180        BatteryManager batteryManager = mock(BatteryManager.class);
181        when(batteryManager.isCharging()).thenReturn(true);
182        doReturn(batteryManager).when(service).getSystemService(Context.BATTERY_SERVICE);
183        UserManager userManager = mock(UserManager.class);
184        when(userManager.getUsers()).thenReturn(new ArrayList<>());
185        doReturn(userManager).when(service).getSystemService(Context.USER_SERVICE);
186        doReturn(mSsm).when(service).getSystemService(Context.STORAGE_STATS_SERVICE);
187        doReturn(mock(StorageManager.class))
188                .when(service)
189                .getSystemService(Context.STORAGE_SERVICE);
190
191        MockContentResolver cr = new MockContentResolver();
192        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
193        doReturn(cr).when(service).getContentResolver();
194
195        PackageManager pm = mock(PackageManager.class);
196        VolumeInfo volumeInfo =
197                new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL, VolumeInfo.TYPE_PRIVATE, null, null);
198        when(pm.getPrimaryStorageCurrentVolume()).thenReturn(volumeInfo);
199        doReturn(pm).when(service).getPackageManager();
200
201        doReturn(0).when(service).getUserId();
202
203        // UGGGGGHHHHHHH! jobFinished is a final method on JobService which crashes when called if
204        // the JobService isn't initialized for real. ServiceTestCase doesn't let us initialize a
205        // service which is built into the framework without crashing, though, so we can't make a
206        // real initialized service.
207        //
208        // And so, we use reflection to set the JobServiceEngine, which is used by the final method,
209        // to be something which won't crash when called.
210        final Field field = JobService.class.getDeclaredField("mEngine");
211        field.setAccessible(true);
212        field.set(service, mock(JobServiceEngine.class));
213
214        // Note: This won't clobber your on-device cache file because, technically,
215        // FrameworkServicesTests don't have write permission to actually overwrite the cache file.
216        // We log and fail on the write silently in this case.
217        service.onStartJob(null);
218    }
219
220    private void writeDataToFile(File f, String data) throws Exception{
221        PrintStream out = new PrintStream(f);
222        out.print(data);
223        out.close();
224    }
225
226    private JSONObject getJsonOutput() throws Exception {
227        return new JSONObject(IoUtils.readFileAsString(mInputFile.getAbsolutePath()));
228    }
229}
230