• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.file.backends;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static org.junit.Assert.assertThrows;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.when;
22 
23 import android.accounts.Account;
24 import android.content.Context;
25 import android.net.Uri;
26 import android.os.Build;
27 import android.os.Build.VERSION_CODES;
28 import androidx.test.core.app.ApplicationProvider;
29 import com.google.common.util.concurrent.Futures;
30 import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
31 import java.io.File;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 import org.robolectric.annotation.Config;
35 
36 /** Test {@link com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri}. */
37 @RunWith(GoogleRobolectricTestRunner.class)
38 @Config(
39     sdk = {VERSION_CODES.M, VERSION_CODES.N, VERSION_CODES.O},
40     shadows = {})
41 public final class AndroidUriTest {
42 
43   private final Context context = ApplicationProvider.getApplicationContext();
44 
45   @Test
builder_unsupportedLocationSuchAsCache_throwsException()46   public void builder_unsupportedLocationSuchAsCache_throwsException() {
47     File file = new File(context.getCacheDir(), "file");
48     assertThrows(IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file));
49   }
50 
51   @Test
builder_missingAccountDirectory_throwsException()52   public void builder_missingAccountDirectory_throwsException() {
53     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), "module/");
54     assertThrows(IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file));
55   }
56 
57   @Test
builder_filesLocation()58   public void builder_filesLocation() {
59     String filePath = "module/shared/directory/file";
60     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
61 
62     Uri uri = AndroidUri.builder(context).fromFile(file).build();
63     assertThat(uri.getScheme()).isEqualTo("android");
64     assertThat(uri.getAuthority()).isEqualTo(context.getPackageName());
65     assertThat(uri.getPath()).isEqualTo("/files/" + filePath);
66     assertThat(uri.toString())
67         .isEqualTo("android://" + context.getPackageName() + "/files/" + filePath);
68   }
69 
70   @Test
builder_cacheLocation()71   public void builder_cacheLocation() {
72     String filePath = "module/shared/directory/file";
73     File file = new File(context.getCacheDir(), filePath);
74 
75     Uri uri = AndroidUri.builder(context).fromFile(file).build();
76     assertThat(uri.getScheme()).isEqualTo("android");
77     assertThat(uri.getAuthority()).isEqualTo(context.getPackageName());
78     assertThat(uri.getPath()).isEqualTo("/cache/" + filePath);
79     assertThat(uri.toString())
80         .isEqualTo("android://" + context.getPackageName() + "/cache/" + filePath);
81   }
82 
83   @Test
builder_filesLocation_withEmailAccount()84   public void builder_filesLocation_withEmailAccount() {
85     String filePath = "module/google.com:<internal>@gmail.com/directory/file";
86     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
87 
88     Uri uri = AndroidUri.builder(context).fromFile(file).build();
89     assertThat(uri.getScheme()).isEqualTo("android");
90     assertThat(uri.getAuthority()).isEqualTo(context.getPackageName());
91     assertThat(uri.getPath()).isEqualTo("/files/" + filePath);
92     assertThat(uri.toString())
93         .isEqualTo("android://" + context.getPackageName() + "/files/" + Uri.encode(filePath, "/"));
94   }
95 
96   @Test
builder_filesLocation_withEmptyAccountName()97   public void builder_filesLocation_withEmptyAccountName() {
98     String filePath = "module/google.com:/directory/file";
99     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
100 
101     assertThrows(
102         IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file).build());
103   }
104 
105   @Test
builder_filesLocation_withEmptyAccountType()106   public void builder_filesLocation_withEmptyAccountType() {
107     String filePath = "module/:<internal>@gmail.com/directory/file";
108     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
109 
110     assertThrows(
111         IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file).build());
112   }
113 
114   @Test
builder_filesLocation_withMalformedAccount()115   public void builder_filesLocation_withMalformedAccount() {
116     String filePath = "module/MALFORMED/directory/file";
117     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
118 
119     assertThrows(
120         IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file).build());
121   }
122 
123   @Test
124   @Config(sdk = Build.VERSION_CODES.N)
builder_directBootFilesDirectory()125   public void builder_directBootFilesDirectory() {
126     String filePath = "module/shared/directory/file";
127     File root = new File(AndroidFileEnvironment.getDeviceProtectedDataDir(context), "files");
128     File file = new File(root, filePath);
129 
130     Uri uri = AndroidUri.builder(context).fromFile(file).build();
131     assertThat(uri.getScheme()).isEqualTo("android");
132     assertThat(uri.getAuthority()).isEqualTo(context.getPackageName());
133     assertThat(uri.getPath()).isEqualTo("/directboot-files/" + filePath);
134     assertThat(uri.toString())
135         .isEqualTo("android://" + context.getPackageName() + "/directboot-files/" + filePath);
136   }
137 
138   @Test
139   @Config(sdk = Build.VERSION_CODES.N)
builder_directBootCacheDirectory()140   public void builder_directBootCacheDirectory() {
141     String filePath = "module/shared/directory/file";
142     File root = new File(AndroidFileEnvironment.getDeviceProtectedDataDir(context), "cache");
143     File file = new File(root, filePath);
144 
145     Uri uri = AndroidUri.builder(context).fromFile(file).build();
146     assertThat(uri.getScheme()).isEqualTo("android");
147     assertThat(uri.getAuthority()).isEqualTo(context.getPackageName());
148     assertThat(uri.getPath()).isEqualTo("/directboot-cache/" + filePath);
149     assertThat(uri.toString())
150         .isEqualTo("android://" + context.getPackageName() + "/directboot-cache/" + filePath);
151   }
152 
153   @Test
builder_allowsEmptyRelativePath()154   public void builder_allowsEmptyRelativePath() {
155     String filePath = "module/shared/";
156     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
157     Uri uri = AndroidUri.builder(context).fromFile(file).build();
158     assertThat(uri.getPath()).isEqualTo("/files/" + filePath);
159   }
160 
161   @Test
builder_unsupportedModule_throwsException()162   public void builder_unsupportedModule_throwsException() {
163     String filePath = "shared/shared/file";
164     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
165     assertThrows(IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file));
166   }
167 
168   @Test
builder_fromManagedSharedFile_doesNotRequireAccountManager()169   public void builder_fromManagedSharedFile_doesNotRequireAccountManager() {
170     String filePath = "/managed/module/shared/file";
171     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
172     Uri uri = AndroidUri.builder(context).fromFile(file).build();
173     assertThat(uri.getPath()).isEqualTo(filePath);
174   }
175 
176   @Test
builder_fromManagedAccountFile_requiresAccountManager()177   public void builder_fromManagedAccountFile_requiresAccountManager() {
178     String filePath = "/managed/module/0/file";
179     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
180 
181     assertThrows(
182         IllegalArgumentException.class, () -> AndroidUri.builder(context).fromFile(file).build());
183   }
184 
185   @Test
builder_fromManagedFile_readsFromAccountManager()186   public void builder_fromManagedFile_readsFromAccountManager() {
187     Account account = new Account("<internal>@gmail.com", "google.com");
188     AccountManager mockManager = mock(AccountManager.class);
189     when(mockManager.getAccount(123)).thenReturn(Futures.immediateFuture(account));
190 
191     String filePath = "/managed/module/123/file";
192     File file = new File(AndroidFileEnvironment.getFilesDirWithPreNWorkaround(context), filePath);
193 
194     Uri uri = AndroidUri.builder(context).fromFile(file, mockManager).build();
195     assertThat(getPathFragment(uri, 2)).isEqualTo("google.com:<internal>@gmail.com");
196   }
197 
198   @Test
builder_componentsAreSetByDefault()199   public void builder_componentsAreSetByDefault() {
200     Uri uri = AndroidUri.builder(context).build();
201     assertThat(uri.getScheme()).isEqualTo("android");
202     assertThat(uri.getAuthority()).isEqualTo(context.getPackageName());
203     assertThat(uri.getPath()).isEqualTo("/files/common/shared/");
204     assertThat(uri.toString())
205         .isEqualTo("android://" + context.getPackageName() + "/files/common/shared/");
206   }
207 
208   @Test
builder_setLocation_expectedUsage()209   public void builder_setLocation_expectedUsage() {
210     Uri uri = AndroidUri.builder(context).setInternalLocation().build();
211     assertThat(getPathFragment(uri, 0)).isEqualTo("files");
212   }
213 
214   @Test
builder_setLocation_externalLocation()215   public void builder_setLocation_externalLocation() {
216     Uri uri = AndroidUri.builder(context).setExternalLocation().build();
217     assertThat(getPathFragment(uri, 0)).isEqualTo("external");
218   }
219 
220   @Test
builder_setLocation_managed()221   public void builder_setLocation_managed() {
222     Uri uri = AndroidUri.builder(context).setManagedLocation().build();
223     assertThat(getPathFragment(uri, 0)).isEqualTo("managed");
224   }
225 
226   @Test
builder_setModule_expectedUsage()227   public void builder_setModule_expectedUsage() {
228     Uri uri = AndroidUri.builder(context).setModule("testmodule").build();
229     assertThat(getPathFragment(uri, 1)).isEqualTo("testmodule");
230   }
231 
232   @Test
builder_setModule_isValidated()233   public void builder_setModule_isValidated() {
234     assertThrows(
235         IllegalArgumentException.class, () -> AndroidUri.builder(context).setModule("").build());
236   }
237 
238   @Test
builder_setModule_doesNotCollideWithManagedLocation()239   public void builder_setModule_doesNotCollideWithManagedLocation() {
240     assertThrows(
241         IllegalArgumentException.class,
242         () -> AndroidUri.builder(context).setModule("managed").build());
243   }
244 
245   @Test
builder_sharedAccount_isSerializedAsShared()246   public void builder_sharedAccount_isSerializedAsShared() {
247     Uri uri = AndroidUri.builder(context).setAccount(AndroidUri.SHARED_ACCOUNT).build();
248     assertThat(getPathFragment(uri, 2)).isEqualTo("shared");
249   }
250 
251   @Test
builder_setAccount_isValidated()252   public void builder_setAccount_isValidated() {
253     assertThrows(
254         IllegalArgumentException.class,
255         () -> AndroidUri.builder(context).setAccount(new Account("", "")).build());
256   }
257 
258   @Test
builder_setRelativePath_expectedUsage()259   public void builder_setRelativePath_expectedUsage() {
260     Uri uri = AndroidUri.builder(context).setRelativePath("testfile").build();
261     assertThat(getPathFragment(uri, 3)).isEqualTo("testfile");
262   }
263 
264   @Test
validateLocation_onlyAllowsPermittedLocations()265   public void validateLocation_onlyAllowsPermittedLocations() {
266     AndroidUri.validateLocation("files");
267     AndroidUri.validateLocation("cache");
268     AndroidUri.validateLocation("external");
269     AndroidUri.validateLocation("directboot-files");
270     AndroidUri.validateLocation("directboot-cache");
271     AndroidUri.validateLocation("managed");
272     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateLocation(""));
273     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateLocation("other"));
274   }
275 
276   @Test
validateModule_disallowsReservedModules()277   public void validateModule_disallowsReservedModules() {
278     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("reserved"));
279     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("RESERVED"));
280   }
281 
282   @Test
validateModule_allowsNonEmptyLowercaseLetters()283   public void validateModule_allowsNonEmptyLowercaseLetters() {
284     AndroidUri.validateModule("a");
285     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule(""));
286     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("A"));
287     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("Aa"));
288 
289     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("mymodule0"));
290     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("myModule"));
291   }
292 
293   @Test
validateModule_allowsInterleavedUnderscores()294   public void validateModule_allowsInterleavedUnderscores() {
295     AndroidUri.validateModule("mymodule");
296     AndroidUri.validateModule("my_module");
297     AndroidUri.validateModule("my_module_two");
298 
299     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("my module"));
300     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("my-module"));
301 
302     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("mymodule_"));
303     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("_mymodule"));
304     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("my_module_"));
305     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("_my_module"));
306     assertThrows(IllegalArgumentException.class, () -> AndroidUri.validateModule("my__module"));
307   }
308 
309   @Test
validateRelativePath_isNoOp()310   public void validateRelativePath_isNoOp() {
311     AndroidUri.validateRelativePath("");
312     AndroidUri.validateRelativePath("myFile");
313     AndroidUri.validateRelativePath("myDir/myFile");
314     AndroidUri.validateRelativePath("myDir/../myFile");
315     AndroidUri.validateRelativePath("/myDir/myFile");
316   }
317 
318   @Test
builder_setPackage_expectedUsage()319   public void builder_setPackage_expectedUsage() {
320     Uri uri = AndroidUri.builder(context).setPackage("testpackage").build();
321     assertThat(uri.getAuthority()).isEqualTo("testpackage");
322   }
323 
324   /**
325    * Utility method to get the i'th path fragment of {@code URI}. May throw exception if the URI is
326    * null, its path is null, or it does not have {@code index} path fragments.
327    */
getPathFragment(Uri uri, int index)328   private static String getPathFragment(Uri uri, int index) {
329     // A valid path begins with "/", so +1 is required to offset the first split element ("")
330     return uri.getPath().split("/")[index + 1];
331   }
332 }
333