• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tests.sdksandbox;
18 
19 import static android.os.storage.StorageManager.UUID_DEFAULT;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertThrows;
24 
25 import android.app.sdksandbox.SdkSandboxManager;
26 import android.app.sdksandbox.testutils.FakeRemoteSdkCallback;
27 import android.app.usage.StorageStats;
28 import android.app.usage.StorageStatsManager;
29 import android.content.Context;
30 import android.os.Binder;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.Process;
34 import android.os.UserHandle;
35 
36 import androidx.test.core.app.ApplicationProvider;
37 import androidx.test.platform.app.InstrumentationRegistry;
38 
39 import junit.framework.AssertionFailedError;
40 
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.junit.runners.JUnit4;
45 
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.FileNotFoundException;
49 
50 @RunWith(JUnit4.class)
51 public class SdkSandboxStorageTestApp {
52 
53     private static final String CODE_PROVIDER_PACKAGE =
54             "com.android.tests.codeprovider.storagetest";
55     private static final String CODE_PROVIDER_KEY = "sdk-provider-class";
56     private static final String CODE_PROVIDER_CLASS =
57             "com.android.tests.codeprovider.storagetest.StorageTestSandboxedSdkProvider";
58 
59     private static final String BUNDLE_KEY_PHASE_NAME = "phase-name";
60 
61     private static final String JAVA_FILE_PERMISSION_DENIED_MSG =
62             "open failed: EACCES (Permission denied)";
63     private static final String JAVA_FILE_NOT_FOUND_MSG =
64             "open failed: ENOENT (No such file or directory)";
65 
66     private SdkSandboxManager mSdkSandboxManager;
67 
68     @Before
setup()69     public void setup() {
70         Context context = ApplicationProvider.getApplicationContext();
71         mSdkSandboxManager = context.getSystemService(
72                 SdkSandboxManager.class);
73         assertThat(mSdkSandboxManager).isNotNull();
74     }
75 
76     // Run a phase of the test inside the code loaded for this app
runPhaseInsideCode(IBinder token, String phaseName)77     private void runPhaseInsideCode(IBinder token, String phaseName) {
78         Bundle bundle = new Bundle();
79         bundle.putString(BUNDLE_KEY_PHASE_NAME, phaseName);
80         mSdkSandboxManager.requestSurfacePackage(token, new Binder(), 0, bundle);
81     }
82 
83     @Test
testSdkSandboxDataRootDirectory_IsNotAccessibleByApps()84     public void testSdkSandboxDataRootDirectory_IsNotAccessibleByApps() throws Exception {
85         assertDirIsNotAccessible("/data/misc_ce/0/sdksandbox");
86         assertDirIsNotAccessible("/data/misc_de/0/sdksandbox");
87     }
88 
89     @Test
testSdkSandboxDataAppDirectory_SharedStorageIsUsable()90     public void testSdkSandboxDataAppDirectory_SharedStorageIsUsable() throws Exception {
91         // First load code
92         Bundle params = new Bundle();
93         params.putString(CODE_PROVIDER_KEY, CODE_PROVIDER_CLASS);
94         FakeRemoteSdkCallback callback = new FakeRemoteSdkCallback();
95         mSdkSandboxManager.loadSdk(CODE_PROVIDER_PACKAGE, params, callback);
96         IBinder codeToken = callback.getSdkToken();
97 
98         // Run phase inside the code
99         runPhaseInsideCode(codeToken, "testSdkSandboxDataAppDirectory_SharedStorageIsUsable");
100 
101         // Wait for code to finish handling the request
102         assertThat(callback.isRequestSurfacePackageSuccessful()).isFalse();
103     }
104 
105     @Test
testSdkDataIsAttributedToApp()106     public void testSdkDataIsAttributedToApp() throws Exception {
107         // First load sdk
108         Bundle params = new Bundle();
109         params.putString(CODE_PROVIDER_KEY, CODE_PROVIDER_CLASS);
110         FakeRemoteSdkCallback callback = new FakeRemoteSdkCallback();
111         mSdkSandboxManager.loadSdk(CODE_PROVIDER_PACKAGE, params, callback);
112         IBinder codeToken = callback.getSdkToken();
113 
114         final StorageStatsManager stats = InstrumentationRegistry.getInstrumentation().getContext()
115                                                 .getSystemService(StorageStatsManager.class);
116         int uid = Process.myUid();
117         UserHandle user = Process.myUserHandle();
118 
119         // Have the sdk use up space
120         final StorageStats initialAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid);
121         final StorageStats initialUserStats = stats.queryStatsForUser(UUID_DEFAULT, user);
122         runPhaseInsideCode(codeToken, "testSdkDataIsAttributedToApp");
123 
124         // Wait for sdk to finish handling the request
125         callback.isRequestSurfacePackageSuccessful();
126         final StorageStats finalAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid);
127         final StorageStats finalUserStats = stats.queryStatsForUser(UUID_DEFAULT, user);
128 
129         // Verify the space used with a few hundred kilobytes error margin
130         long deltaAppSize = 2000000;
131         long deltaCacheSize = 1000000;
132         long errorMarginSize = 100000;
133         assertMostlyEquals(deltaAppSize,
134                     finalAppStats.getDataBytes() - initialAppStats.getDataBytes(),
135                            errorMarginSize);
136         assertMostlyEquals(deltaAppSize,
137                     finalUserStats.getDataBytes() - initialUserStats.getDataBytes(),
138                            errorMarginSize);
139         assertMostlyEquals(deltaCacheSize,
140                     finalAppStats.getCacheBytes() - initialAppStats.getCacheBytes(),
141                            errorMarginSize);
142         assertMostlyEquals(deltaCacheSize,
143                     finalUserStats.getCacheBytes() - initialUserStats.getCacheBytes(),
144                            errorMarginSize);
145     }
146 
assertDirIsNotAccessible(String path)147     private static void assertDirIsNotAccessible(String path) {
148         // Trying to access a file that does not exist in that directory, it should return
149         // permission denied not file not found.
150         Exception exception = assertThrows(FileNotFoundException.class, () -> {
151             new FileInputStream(new File(path, "FILE_DOES_NOT_EXIST"));
152         });
153         assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG);
154         assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG);
155 
156         assertThat(new File(path).canExecute()).isFalse();
157     }
158 
assertMostlyEquals(long expected, long actual, long delta)159     public static void assertMostlyEquals(long expected, long actual, long delta) {
160         if (Math.abs(expected - actual) > delta) {
161             throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual);
162         }
163     }
164 }
165