Skip to content

Commit 5acf36a

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Make TestStorage use internal storage for output files when running as non system user.
Previously TestStorage always used the legacy external storage directory to store both input and output files. However there has been stability issues with creating directories in this location in automotive platforms where tests always run as a secondary user. This commit changes the logic to use the current app's local cache directory when running as a secondary user. The hope is this can be done without any corresponding host-side adjustment, as host side code paths use 'adb shell content read' commands to fetch output files when running as a secondary user - making it not-dependent on absolute storage location. This commit also attempts to simplify the storage ContentProvider logic. It previously attempted to create directories in multiple locations, had hard coded assumptions around mounting locations etc. PiperOrigin-RevId: 627060015
1 parent 0733bc6 commit 5acf36a

14 files changed

+131
-124
lines changed

services/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
**Bug Fixes**
88

9+
* TestStorage: use local cache dir to store output files when running as non system user
10+
911
**New Features**
1012

1113
**Breaking Changes**

services/storage/java/androidx/test/services/storage/TestStorage.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,10 @@ public Uri getOutputFileUri(@NonNull String pathname) {
125125
@Override
126126
public boolean isTestStorageFilePath(@NonNull String pathname) {
127127
File onDevicePathRoot =
128-
new File(HostedFile.getRootDirectory(), TestStorageConstants.ON_DEVICE_PATH_ROOT);
128+
new File(
129+
HostedFile.getOutputRootDirectory(
130+
InstrumentationRegistry.getInstrumentation().getTargetContext()),
131+
TestStorageConstants.ON_DEVICE_PATH_ROOT);
129132
// Append a trailing slash because ON_DEVICE_PATH_ROOT has a trailing slash. If pathname already
130133
// has a trailing slash or other suffix, this won't affect the startsWith() matching logic.
131134
pathname = pathname + "/";
@@ -318,6 +321,7 @@ public Map<String, Serializable> getOutputProperties() {
318321
* @return an InputStream to the given test file.
319322
* @hide
320323
*/
324+
// TODO(b/335660740): remove this unused method
321325
@RestrictTo(Scope.LIBRARY)
322326
@Override
323327
public InputStream openInternalInputFile(String pathname) throws FileNotFoundException {

services/storage/java/androidx/test/services/storage/file/HostedFile.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
*/
1616
package androidx.test.services.storage.file;
1717

18+
import android.content.Context;
1819
import android.net.Uri;
20+
import android.os.Build.VERSION;
1921
import android.os.Environment;
22+
import android.os.UserManager;
2023
import android.provider.OpenableColumns;
24+
import android.util.Log;
2125
import androidx.test.annotation.ExperimentalTestApi;
2226
import androidx.test.services.storage.TestStorageConstants;
2327
import java.io.File;
@@ -26,6 +30,8 @@
2630
@ExperimentalTestApi
2731
public final class HostedFile {
2832

33+
private static final String TAG = "HostedFile";
34+
2935
/** An enum of the columns returned by the hosted file service. */
3036
public enum HostedFileColumn {
3137
NAME("name", String.class, 3 /* Cursor.FIELD_TYPE_STRING since api 11 */, 0),
@@ -132,10 +138,40 @@ public static Uri buildUri(FileHost host, String fileName) {
132138
.build();
133139
}
134140

135-
public static File getRootDirectory() {
141+
public static File getInputRootDirectory(Context context) {
142+
// always use external storage dir for input
143+
Log.i(
144+
TAG,
145+
"Choosing external storage as root dir for input: "
146+
+ Environment.getExternalStorageDirectory().getAbsolutePath());
136147
return Environment.getExternalStorageDirectory();
137148
}
138149

150+
public static File getOutputRootDirectory(Context context) {
151+
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
152+
if (VERSION.SDK_INT < 23) {
153+
Log.i(
154+
TAG,
155+
"Running on API < 23; Choosing external storage as output root dir: "
156+
+ Environment.getExternalStorageDirectory().getAbsolutePath());
157+
return Environment.getExternalStorageDirectory();
158+
} else if (userManager.isSystemUser()) {
159+
Log.i(
160+
TAG,
161+
"System user detected. Choosing external storage as output root dir: "
162+
+ Environment.getExternalStorageDirectory().getAbsolutePath());
163+
return Environment.getExternalStorageDirectory();
164+
} else {
165+
// using legacy external storage for output in automotive devices where tests run as
166+
// a secondary user has been flaky. So use local storage instead.
167+
Log.i(
168+
TAG,
169+
"Secondary user detected. Choosing local storage as output root dir: "
170+
+ context.getCacheDir().getAbsolutePath());
171+
return context.getCacheDir();
172+
}
173+
}
174+
139175
private static <T> T checkNotNull(T reference) {
140176
if (reference == null) {
141177
throw new NullPointerException();

services/storage/java/androidx/test/services/storage/provider/AbstractFileContentProvider.java

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import android.content.ContentProvider;
2121
import android.content.ContentValues;
22+
import android.content.Context;
2223
import android.database.Cursor;
2324
import android.database.MatrixCursor;
2425
import android.net.Uri;
@@ -43,52 +44,25 @@
4344
abstract class AbstractFileContentProvider extends ContentProvider {
4445
private static final String TAG = AbstractFileContentProvider.class.getSimpleName();
4546

46-
private final File hostedDirectory;
47-
private final Access access;
47+
private File hostedDirectory;
48+
private Access access;
4849

4950
enum Access {
5051
READ_ONLY,
5152
READ_WRITE
5253
}
5354

54-
/**
55-
* Called during onCreate(). Subclasses should return true if they are ready to serve data and
56-
* false if there is something wrong accessing their data. Such as the sdcard not being mounted.
57-
*/
58-
protected abstract boolean onCreateHook();
55+
/** Returns the root directory where this content provider should manage files from */
56+
protected abstract File getHostedDirectory(Context context);
5957

60-
AbstractFileContentProvider(File hostedDirectory, Access access) {
61-
super();
62-
try {
63-
this.hostedDirectory = checkNotNull(hostedDirectory).getCanonicalFile();
64-
} catch (IOException ioe) {
65-
throw new RuntimeException(ioe);
66-
}
67-
this.access = access;
68-
}
58+
/** Returns the access mode of this content provider */
59+
protected abstract Access getAccess();
6960

7061
@Override
7162
public boolean onCreate() {
72-
if (onCreateHook()) {
73-
if (!hostedDirectory.exists()) {
74-
if (!hostedDirectory.mkdirs()) {
75-
Log.e(TAG, "Cannot create hosted directory: " + hostedDirectory);
76-
return false;
77-
}
78-
}
79-
if (!hostedDirectory.isDirectory()) {
80-
Log.e(TAG, "Hosted directory not a directory: " + hostedDirectory);
81-
return false;
82-
}
83-
if ((Access.READ_WRITE == access) && !hostedDirectory.canWrite()) {
84-
Log.e(TAG, "Hosted directory is not writable and write was requested: " + hostedDirectory);
85-
return false;
86-
}
87-
return true;
88-
} else {
89-
Log.e(TAG, "Subclass claims hosted directory not ready: " + hostedDirectory);
90-
return false;
91-
}
63+
hostedDirectory = getHostedDirectory(getContext());
64+
access = getAccess();
65+
return true;
9266
}
9367

9468
@Override
@@ -107,10 +81,15 @@ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundEx
10781
try {
10882
requestedFile.getParentFile().mkdirs();
10983
if (!requestedFile.getParentFile().exists()) {
84+
Log.e(
85+
TAG,
86+
"Failed to create parent directory "
87+
+ requestedFile.getParentFile().getAbsolutePath());
11088
throw new FileNotFoundException(String.format("No parent directory for '%s'", uri));
11189
}
11290

11391
if (!requestedFile.createNewFile()) {
92+
Log.e(TAG, "Failed to create file" + requestedFile.getAbsolutePath());
11493
throw new FileNotFoundException("Could not create file: " + uri);
11594
}
11695
} catch (IOException ioe) {

services/storage/java/androidx/test/services/storage/provider/ExportTestPropertiesContentProvider.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020
/** Hosts the properties file that are exported to the testing infrastructure for tests. */
2121
public final class ExportTestPropertiesContentProvider extends TestFileContentProvider {
2222

23-
public ExportTestPropertiesContentProvider() {
24-
super(
25-
TestStorageConstants.ON_DEVICE_PATH_TEST_PROPERTIES,
26-
AbstractFileContentProvider.Access.READ_WRITE);
23+
@Override
24+
protected Access getAccess() {
25+
return AbstractFileContentProvider.Access.READ_WRITE;
26+
}
27+
28+
@Override
29+
protected String getHostedRelativePath() {
30+
return TestStorageConstants.ON_DEVICE_PATH_TEST_PROPERTIES;
2731
}
2832
}

services/storage/java/androidx/test/services/storage/provider/InternalUseOnlyFilesContentProvider.java

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,23 @@
1515
*/
1616
package androidx.test.services.storage.provider;
1717

18-
import android.os.Environment;
19-
import android.util.Log;
18+
import android.content.Context;
2019
import androidx.test.services.storage.TestStorageConstants;
2120
import androidx.test.services.storage.file.HostedFile;
2221
import java.io.File;
2322

2423
/** Hosts an SD Card directory for the test framework to read/write internal files to. */
2524
public final class InternalUseOnlyFilesContentProvider extends AbstractFileContentProvider {
26-
private static final String TAG = "InternalUseOnlyFilesContentProvider";
2725

28-
private final File outputDirectory;
29-
30-
public InternalUseOnlyFilesContentProvider() {
31-
super(
32-
new File(HostedFile.getRootDirectory(), TestStorageConstants.ON_DEVICE_PATH_INTERNAL_USE),
33-
AbstractFileContentProvider.Access.READ_WRITE);
34-
outputDirectory =
35-
new File(HostedFile.getRootDirectory(), TestStorageConstants.ON_DEVICE_PATH_INTERNAL_USE);
26+
@Override
27+
protected File getHostedDirectory(Context context) {
28+
return new File(
29+
HostedFile.getOutputRootDirectory(context),
30+
TestStorageConstants.ON_DEVICE_PATH_INTERNAL_USE);
3631
}
3732

3833
@Override
39-
protected boolean onCreateHook() {
40-
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
41-
Log.e(TAG, "sdcard in bad state: " + Environment.getExternalStorageState());
42-
return false;
43-
} else {
44-
if (!outputDirectory.exists()) {
45-
if (!outputDirectory.mkdirs()) {
46-
Log.e(TAG, String.format("'%s': could not create output dir! ", outputDirectory));
47-
return false;
48-
}
49-
}
50-
return true;
51-
}
34+
protected Access getAccess() {
35+
return AbstractFileContentProvider.Access.READ_WRITE;
5236
}
5337
}

services/storage/java/androidx/test/services/storage/provider/TestArgsContentProvider.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
import android.content.ContentProvider;
2121
import android.content.ContentValues;
22+
import android.content.Context;
2223
import android.database.Cursor;
2324
import android.database.MatrixCursor;
2425
import android.net.Uri;
26+
import android.util.Log;
2527
import androidx.test.services.storage.TestStorageConstants;
2628
import androidx.test.services.storage.TestStorageServiceProto.TestArgument;
2729
import androidx.test.services.storage.TestStorageServiceProto.TestArguments;
@@ -108,24 +110,25 @@ public Cursor query(
108110
private Map<String, String> buildArgMapFromFile() {
109111
Map<String, String> argMap = new HashMap<>();
110112

111-
for (TestArgument testArg : readProtoFromFile().getArgList()) {
113+
for (TestArgument testArg : readProtoFromFile(this.getContext()).getArgList()) {
112114
String key = testArg.getName();
113115
String val = testArg.getValue();
114116
argMap.put(key, val);
115117
}
116118
return argMap;
117119
}
118120

119-
private static TestArguments readProtoFromFile() {
121+
private static TestArguments readProtoFromFile(Context context) {
120122
File testArgsFile =
121123
new File(
122-
HostedFile.getRootDirectory(),
124+
HostedFile.getInputRootDirectory(context),
123125
TestStorageConstants.ON_DEVICE_PATH_INTERNAL_USE
124126
+ TestStorageConstants.TEST_ARGS_FILE_NAME);
125127
if (!testArgsFile.exists()) {
126128
return TestArguments.getDefaultInstance();
127129
}
128130
try {
131+
Log.i(TAG, "Parsing test args from " + testArgsFile.getAbsolutePath());
129132
return TestArguments.parseFrom(new FileInputStream(testArgsFile));
130133
} catch (IOException e) {
131134
throw new RuntimeException("Not able to read from file: " + testArgsFile.getName(), e);

services/storage/java/androidx/test/services/storage/provider/TestDataContentProvider.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,22 @@
1515
*/
1616
package androidx.test.services.storage.provider;
1717

18-
import android.os.Environment;
19-
import android.util.Log;
18+
import android.content.Context;
2019
import androidx.test.services.storage.TestStorageConstants;
2120
import androidx.test.services.storage.file.HostedFile;
2221
import java.io.File;
2322

2423
/** Provides access to files in the test data section. */
2524
public final class TestDataContentProvider extends AbstractFileContentProvider {
26-
private static final String TAG = TestDataContentProvider.class.getSimpleName();
2725

28-
public TestDataContentProvider() {
29-
super(
30-
new File(HostedFile.getRootDirectory(), TestStorageConstants.ON_DEVICE_TEST_RUNFILES),
31-
AbstractFileContentProvider.Access.READ_ONLY);
26+
@Override
27+
protected File getHostedDirectory(Context context) {
28+
return new File(
29+
HostedFile.getInputRootDirectory(context), TestStorageConstants.ON_DEVICE_TEST_RUNFILES);
3230
}
3331

3432
@Override
35-
protected boolean onCreateHook() {
36-
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
37-
Log.e(TAG, "sdcard in bad state: " + Environment.getExternalStorageState());
38-
return false;
39-
} else {
40-
return true;
41-
}
33+
protected Access getAccess() {
34+
return Access.READ_ONLY;
4235
}
4336
}

services/storage/java/androidx/test/services/storage/provider/TestFileContentProvider.java

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,19 @@
1515
*/
1616
package androidx.test.services.storage.provider;
1717

18-
import android.os.Environment;
19-
import android.util.Log;
18+
import android.content.Context;
2019
import androidx.test.services.storage.file.HostedFile;
2120
import java.io.File;
2221

2322
/**
2423
* Content Provider that allows access to reading/writing files that were written to disk for tests.
2524
*/
2625
abstract class TestFileContentProvider extends AbstractFileContentProvider {
27-
private static final String TAG = TestFileContentProvider.class.getSimpleName();
2826

29-
private final File outputDirectory;
30-
31-
public TestFileContentProvider(String filePath, AbstractFileContentProvider.Access access) {
32-
super(new File(HostedFile.getRootDirectory(), filePath), access);
33-
outputDirectory = new File(HostedFile.getRootDirectory(), filePath);
34-
}
27+
protected abstract String getHostedRelativePath();
3528

3629
@Override
37-
protected boolean onCreateHook() {
38-
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
39-
Log.e(TAG, "sdcard in bad state: " + Environment.getExternalStorageState());
40-
return false;
41-
} else {
42-
if (!outputDirectory.exists()) {
43-
if (!outputDirectory.mkdirs()) {
44-
Log.e(TAG, String.format("'%s': could not create output dir! ", outputDirectory));
45-
return false;
46-
}
47-
}
48-
return true;
49-
}
30+
protected final File getHostedDirectory(Context context) {
31+
return new File(HostedFile.getOutputRootDirectory(context), getHostedRelativePath());
5032
}
5133
}

services/storage/java/androidx/test/services/storage/provider/TestOutputFilesContentProvider.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020
/** Hosts the output directory for tests. */
2121
public final class TestOutputFilesContentProvider extends TestFileContentProvider {
2222

23-
public TestOutputFilesContentProvider() {
24-
super(
25-
TestStorageConstants.ON_DEVICE_PATH_TEST_OUTPUT,
26-
AbstractFileContentProvider.Access.READ_WRITE);
23+
@Override
24+
protected Access getAccess() {
25+
return AbstractFileContentProvider.Access.READ_WRITE;
26+
}
27+
28+
@Override
29+
protected String getHostedRelativePath() {
30+
return TestStorageConstants.ON_DEVICE_PATH_TEST_OUTPUT;
2731
}
2832
}

0 commit comments

Comments
 (0)