• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 package android.scopedstorage.cts;
17 
18 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
19 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromFile;
20 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_READ_QUERY;
21 import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_WRITE_QUERY;
22 import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY;
23 import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_QUERY;
24 import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
25 import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
26 import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
27 import static android.scopedstorage.cts.lib.TestUtils.DELETE_MEDIA_BY_URI_QUERY;
28 import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY;
29 import static android.scopedstorage.cts.lib.TestUtils.FILE_EXISTS_QUERY;
30 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
31 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_ARGS;
32 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG;
33 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CONTENT;
34 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH;
35 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_URI;
36 import static android.scopedstorage.cts.lib.TestUtils.IS_URI_REDACTED_VIA_FILEPATH;
37 import static android.scopedstorage.cts.lib.TestUtils.IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_READ;
38 import static android.scopedstorage.cts.lib.TestUtils.IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_WRITE;
39 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_READ_QUERY;
40 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
41 import static android.scopedstorage.cts.lib.TestUtils.QUERY_MAX_ROW_ID;
42 import static android.scopedstorage.cts.lib.TestUtils.QUERY_MIN_ROW_ID;
43 import static android.scopedstorage.cts.lib.TestUtils.QUERY_OWNER_PACKAGE_NAMES;
44 import static android.scopedstorage.cts.lib.TestUtils.QUERY_TYPE;
45 import static android.scopedstorage.cts.lib.TestUtils.QUERY_URI;
46 import static android.scopedstorage.cts.lib.TestUtils.QUERY_WITH_ARGS;
47 import static android.scopedstorage.cts.lib.TestUtils.READDIR_QUERY;
48 import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARATOR;
49 import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
50 import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
51 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
52 import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
53 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
54 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
55 
56 import static com.google.common.truth.Truth.assertThat;
57 
58 import android.app.Activity;
59 import android.content.ContentResolver;
60 import android.content.ContentValues;
61 import android.content.Intent;
62 import android.database.Cursor;
63 import android.media.ExifInterface;
64 import android.net.Uri;
65 import android.os.Bundle;
66 import android.os.Environment;
67 import android.os.FileUtils;
68 import android.os.IBinder;
69 import android.os.Parcel;
70 import android.os.ParcelFileDescriptor;
71 import android.os.RemoteException;
72 import android.provider.MediaStore;
73 import android.util.Log;
74 
75 import androidx.annotation.Nullable;
76 import androidx.core.content.FileProvider;
77 
78 import com.google.common.base.Strings;
79 
80 import java.io.File;
81 import java.io.FileDescriptor;
82 import java.io.FileInputStream;
83 import java.io.FileOutputStream;
84 import java.io.IOException;
85 import java.util.ArrayList;
86 import java.util.Collections;
87 import java.util.HashSet;
88 import java.util.Set;
89 import java.util.regex.Matcher;
90 import java.util.regex.Pattern;
91 
92 /**
93  * Helper app for ScopedStorageTest.
94  *
95  * <p>Used to perform ScopedStorageTest functions as a different app. Based on the Query type
96  * app can perform different functions and send the result back to host app.
97  */
98 public class ScopedStorageTestHelper extends Activity {
99     private static final String TAG = "ScopedStorageTestHelper";
100     /**
101      * Regex that matches paths in all well-known package-specific directories,
102      * and which captures the directory type as the first group (data|media|obb) and the
103      * package name as the 2nd group.
104      */
105     private static final Pattern PATTERN_OWNED_PATH = Pattern.compile(
106             "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(data|media|obb)/([^/]+)(/?.*)?");
107 
108     @Override
onCreate(Bundle savedInstanceState)109     public void onCreate(Bundle savedInstanceState) {
110         super.onCreate(savedInstanceState);
111         String queryType = getIntent().getStringExtra(QUERY_TYPE);
112         queryType = queryType == null ? "null" : queryType;
113         Intent returnIntent;
114         try {
115             switch (queryType) {
116                 case READDIR_QUERY:
117                     returnIntent = sendDirectoryEntries(queryType);
118                     break;
119                 case FILE_EXISTS_QUERY:
120                 case CAN_READ_WRITE_QUERY:
121                 case CREATE_FILE_QUERY:
122                 case DELETE_FILE_QUERY:
123                 case DELETE_RECURSIVE_QUERY:
124                 case CAN_OPEN_FILE_FOR_READ_QUERY:
125                 case CAN_OPEN_FILE_FOR_WRITE_QUERY:
126                 case OPEN_FILE_FOR_READ_QUERY:
127                 case OPEN_FILE_FOR_WRITE_QUERY:
128                 case SETATTR_QUERY:
129                     returnIntent = accessFile(queryType);
130                     break;
131                 case DELETE_MEDIA_BY_URI_QUERY:
132                     returnIntent = deleteMediaByUri(queryType);
133                     break;
134                 case EXIF_METADATA_QUERY:
135                     returnIntent = sendMetadata(queryType);
136                     break;
137                 case CREATE_IMAGE_ENTRY_QUERY:
138                     returnIntent = createImageEntry(queryType);
139                     break;
140                 case RENAME_FILE_QUERY:
141                     returnIntent = renameFile(queryType);
142                     break;
143                 case CHECK_DATABASE_ROW_EXISTS_QUERY:
144                     returnIntent = checkDatabaseRowExists(queryType);
145                     break;
146                 case IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_WRITE:
147                 case IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_READ:
148                     returnIntent = isFileDescriptorRedactedForUri(queryType);
149                     break;
150                 case IS_URI_REDACTED_VIA_FILEPATH:
151                     returnIntent = isFilePathForUriRedacted(queryType);
152                     break;
153                 case QUERY_URI:
154                     returnIntent = queryForUri(queryType);
155                     break;
156                 case QUERY_MAX_ROW_ID:
157                 case QUERY_MIN_ROW_ID:
158                     returnIntent = queryRowId(queryType);
159                     break;
160                 case QUERY_OWNER_PACKAGE_NAMES:
161                     returnIntent = queryOwnerPackageNames(queryType);
162                     break;
163                 case QUERY_WITH_ARGS:
164                     returnIntent = queryWithArgs(queryType);
165                     break;
166                 case "null":
167                 default:
168                     throw new IllegalStateException(
169                             "Unknown query received from launcher app: " + queryType);
170             }
171         } catch (Exception e) {
172             returnIntent = new Intent(queryType);
173             returnIntent.putExtra(INTENT_EXCEPTION, e);
174         }
175         sendBroadcast(returnIntent);
176     }
177 
queryForUri(String queryType)178     private Intent queryForUri(String queryType) {
179         final Intent intent = new Intent(queryType);
180         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
181 
182         try {
183             final Cursor c = getContentResolver().query(uri, null, null, null);
184             intent.putExtra(queryType, c != null && c.moveToFirst());
185         } catch (Exception e) {
186             intent.putExtra(INTENT_EXCEPTION, e);
187         }
188 
189         return intent;
190     }
191 
queryRowId(String queryType)192     private Intent queryRowId(String queryType) {
193         // Ensure M_E_S permission has been granted.
194         assertThat(Environment.isExternalStorageManager()).isTrue();
195         final Intent intent = new Intent(queryType);
196         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
197         final Bundle bundle = createQueryArgs(queryType);
198         try {
199             final Cursor c = getContentResolver().query(uri,
200                     new String[]{MediaStore.Files.FileColumns._ID}, bundle, null);
201             if (c != null && c.moveToFirst()) {
202                 intent.putExtra(queryType, c.getLong(0));
203             }
204         } catch (Exception e) {
205             intent.putExtra(INTENT_EXCEPTION, e);
206         }
207 
208         return intent;
209     }
210 
queryOwnerPackageNames(String queryType)211     private Intent queryOwnerPackageNames(String queryType) {
212         final Intent intent = new Intent(queryType);
213         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
214 
215         try {
216             final Cursor c = getContentResolver().query(uri,
217                     new String[]{MediaStore.MediaColumns.OWNER_PACKAGE_NAME}, null, null);
218             final Set<String> ownerPackageNames = new HashSet<>();
219             while (c.moveToNext()) {
220                 final String ownerPackageName = c.getString(0);
221                 if (!Strings.isNullOrEmpty(ownerPackageName)) {
222                     ownerPackageNames.add(ownerPackageName);
223                 }
224             }
225             intent.putExtra(queryType, ownerPackageNames.toArray(new String[0]));
226         } catch (Exception e) {
227             intent.putExtra(INTENT_EXCEPTION, e);
228         }
229 
230         return intent;
231     }
232 
deleteMediaByUri(String queryType)233     private Intent deleteMediaByUri(String queryType) {
234         final Intent intent = new Intent(queryType);
235         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
236 
237         try {
238             int rowsDeleted = getContentResolver().delete(uri, null);
239             intent.putExtra(queryType, rowsDeleted);
240         } catch (Exception e) {
241             intent.putExtra(INTENT_EXCEPTION, e);
242         }
243 
244         return intent;
245     }
246 
queryWithArgs(String queryType)247     private Intent queryWithArgs(String queryType) {
248         final Intent intent = new Intent(queryType);
249         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
250         final Bundle args = getIntent().getBundleExtra(INTENT_EXTRA_ARGS);
251         try {
252             final Cursor c = getContentResolver().query(uri,
253                     new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, args, null);
254             intent.putExtra(queryType, c.getCount());
255         } catch (Exception e) {
256             intent.putExtra(INTENT_EXCEPTION, e);
257         }
258 
259         return intent;
260     }
261 
createQueryArgs(String queryType)262     private Bundle createQueryArgs(String queryType) {
263         switch (queryType){
264             case QUERY_MAX_ROW_ID:
265                 return createQueryArgToRetrieveMaximumRowId();
266             case QUERY_MIN_ROW_ID:
267                 return createQueryArgToRetrieveMinimumRowId();
268             default:
269                 throw new IllegalStateException(
270                         "Unknown query type received from launcher app: " + queryType);
271         }
272     }
273 
createQueryArgToRetrieveMinimumRowId()274     private Bundle createQueryArgToRetrieveMinimumRowId() {
275         final Bundle queryArgs = new Bundle();
276         queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
277                 MediaStore.Files.FileColumns._ID + " ASC");
278         queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
279         return queryArgs;
280     }
281 
createQueryArgToRetrieveMaximumRowId()282     private Bundle createQueryArgToRetrieveMaximumRowId() {
283         final Bundle queryArgs = new Bundle();
284         queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
285                 MediaStore.Files.FileColumns._ID + " DESC");
286         queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
287         return queryArgs;
288     }
289 
isFileDescriptorRedactedForUri(String queryType)290     private Intent isFileDescriptorRedactedForUri(String queryType) {
291         final Intent intent = new Intent(queryType);
292         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
293 
294         try {
295             final String mode = queryType.equals(IS_URI_REDACTED_VIA_FILE_DESCRIPTOR_FOR_WRITE)
296                     ? "w" : "r";
297             try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, mode)) {
298                 FileDescriptor fd = pfd.getFileDescriptor();
299                 ExifInterface exifInterface = new ExifInterface(fd);
300                 intent.putExtra(queryType, exifInterface.getGpsDateTime() == -1);
301             }
302         } catch (Exception e) {
303             intent.putExtra(INTENT_EXCEPTION, e);
304         }
305 
306         return intent;
307     }
308 
isFilePathForUriRedacted(String queryType)309     private Intent isFilePathForUriRedacted(String queryType) {
310         final Intent intent = new Intent(queryType);
311         final Uri uri = getIntent().getParcelableExtra(INTENT_EXTRA_URI);
312 
313         try {
314             final Cursor c = getContentResolver().query(uri, null, null, null);
315             if (!c.moveToFirst()) {
316                 intent.putExtra(INTENT_EXCEPTION, new IOException(""));
317                 return intent;
318             }
319             final String path = c.getString(c.getColumnIndex(MediaStore.MediaColumns.DATA));
320             ExifInterface redactedExifInf = new ExifInterface(path);
321             intent.putExtra(queryType, redactedExifInf.getGpsDateTime() == -1);
322         } catch (Exception e) {
323             intent.putExtra(INTENT_EXCEPTION, e);
324         }
325 
326         return intent;
327     }
328 
sendMetadata(String queryType)329     private Intent sendMetadata(String queryType) throws IOException {
330         final Intent intent = new Intent(queryType);
331         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
332             final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
333             if (EXIF_METADATA_QUERY.equals(queryType)) {
334                 intent.putExtra(queryType, getExifMetadataFromFile(new File(filePath)));
335             }
336         } else {
337             throw new IllegalStateException(
338                     EXIF_METADATA_QUERY + ": File path not set from launcher app");
339         }
340         return intent;
341     }
342 
sendDirectoryEntries(String queryType)343     private Intent sendDirectoryEntries(String queryType) throws IOException {
344         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
345             final String directoryPath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
346             ArrayList<String> directoryEntriesList = new ArrayList<>();
347             if (queryType.equals(READDIR_QUERY)) {
348                 final String[] directoryEntries = new File(directoryPath).list();
349                 if (directoryEntries == null) {
350                     throw new IOException(
351                             "I/O exception while listing entries for " + directoryPath);
352                 }
353                 Collections.addAll(directoryEntriesList, directoryEntries);
354             }
355             final Intent intent = new Intent(queryType);
356             intent.putStringArrayListExtra(queryType, directoryEntriesList);
357             return intent;
358         } else {
359             throw new IllegalStateException(
360                     READDIR_QUERY + ": Directory path not set from launcher app");
361         }
362     }
363 
createImageEntry(String queryType)364     private Intent createImageEntry(String queryType) throws Exception {
365         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
366             final String path = getIntent().getStringExtra(INTENT_EXTRA_PATH);
367             final String relativePath = path.substring(0, path.lastIndexOf('/'));
368             final String name = path.substring(path.lastIndexOf('/') + 1);
369 
370             final ContentValues values = new ContentValues();
371             values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
372             values.put(MediaStore.Images.Media.RELATIVE_PATH, relativePath);
373             values.put(MediaStore.Images.Media.DISPLAY_NAME, name);
374 
375             final Uri imageUri = getContentResolver().insert(getImageContentUri(), values);
376 
377             final Intent intent = new Intent(queryType);
378             intent.putExtra(queryType, imageUri.toString());
379             return intent;
380         } else {
381             throw new IllegalStateException(
382                     CREATE_IMAGE_ENTRY_QUERY + ": File path not set from launcher app");
383         }
384     }
385 
accessFile(String queryType)386     private Intent accessFile(String queryType) throws IOException, RemoteException {
387         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
388             final String packageName = getIntent().getStringExtra(INTENT_EXTRA_CALLING_PKG);
389             final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
390             final File file = new File(filePath);
391             final Intent intent = new Intent(queryType);
392             switch (queryType) {
393                 case FILE_EXISTS_QUERY:
394                     intent.putExtra(queryType, file.exists());
395                     return intent;
396                 case CAN_READ_WRITE_QUERY:
397                     intent.putExtra(queryType, file.exists() && file.canRead() && file.canWrite());
398                     return intent;
399                 case CREATE_FILE_QUERY:
400                     maybeCreateParentDirInAndroid(file);
401                     if (!file.getParentFile().exists()) {
402                         file.getParentFile().mkdirs();
403                     }
404                     boolean success = file.createNewFile();
405                     if (success && getIntent().hasExtra(INTENT_EXTRA_CONTENT)) {
406                         success = createFileContent(file);
407                     }
408                     intent.putExtra(queryType, success);
409                     return intent;
410                 case DELETE_FILE_QUERY:
411                     intent.putExtra(queryType, file.delete());
412                     return intent;
413                 case DELETE_RECURSIVE_QUERY:
414                     intent.putExtra(queryType, deleteRecursively(file));
415                     return intent;
416                 case SETATTR_QUERY:
417                     int newTimeMillis = 12345000;
418                     intent.putExtra(queryType, file.setLastModified(newTimeMillis));
419                     return intent;
420                 case CAN_OPEN_FILE_FOR_READ_QUERY:
421                     intent.putExtra(queryType, canOpen(file, false));
422                     return intent;
423                 case CAN_OPEN_FILE_FOR_WRITE_QUERY:
424                     intent.putExtra(queryType, canOpen(file, true));
425                     return intent;
426                 case OPEN_FILE_FOR_READ_QUERY:
427                 case OPEN_FILE_FOR_WRITE_QUERY:
428                     Uri contentUri = FileProvider.getUriForFile(getApplicationContext(),
429                             getApplicationContext().getPackageName(), file);
430                     intent.putExtra(queryType, contentUri);
431                     // Grant permission to the possible instrumenting test apps
432                     if (packageName != null) {
433                         getApplicationContext().grantUriPermission(packageName,
434                                 contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
435                                 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
436                     }
437                     return intent;
438                 default:
439                     throw new IllegalStateException(
440                             "Unknown query received from launcher app: " + queryType);
441             }
442         } else {
443             throw new IllegalStateException(queryType + ": File path not set from launcher app");
444         }
445     }
446 
createFileContent(File file)447     private boolean createFileContent(File file) throws RemoteException {
448         final Bundle content = getIntent().getBundleExtra(
449                 INTENT_EXTRA_CONTENT);
450         IBinder binder = content.getBinder(INTENT_EXTRA_CONTENT);
451         Parcel reply = Parcel.obtain();
452         binder.transact(IBinder.FIRST_CALL_TRANSACTION, Parcel.obtain(), reply, 0);
453         try (ParcelFileDescriptor inputPFD = reply.readFileDescriptor();
454              FileInputStream fileInputStream = new FileInputStream(
455                      inputPFD.getFileDescriptor());
456              FileOutputStream outputStream = new FileOutputStream(file)) {
457             long copied = FileUtils.copy(fileInputStream, outputStream);
458             outputStream.getFD().sync();
459             return copied > 0;
460         } catch (Exception e) {
461             Log.e(TAG, e.getMessage(), e);
462             return false;
463         }
464     }
465 
renameFile(String queryType)466     private Intent renameFile(String queryType) {
467         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
468             String[] paths = getIntent().getStringExtra(INTENT_EXTRA_PATH)
469                     .split(RENAME_FILE_PARAMS_SEPARATOR);
470             File src = new File(paths[0]);
471             File dst = new File(paths[1]);
472             boolean result = src.renameTo(dst);
473             final Intent intent = new Intent(queryType);
474             intent.putExtra(queryType, result);
475             return intent;
476         } else {
477             throw new IllegalStateException(
478                     queryType + ": File paths not set from launcher app");
479         }
480     }
481 
checkDatabaseRowExists(String queryType)482     private Intent checkDatabaseRowExists(String queryType) {
483         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
484             final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
485             boolean result =
486                     getFileRowIdFromDatabase(getContentResolver(), new File(filePath)) != -1;
487             final Intent intent = new Intent(queryType);
488             intent.putExtra(queryType, result);
489             return intent;
490         } else {
491             throw new IllegalStateException(
492                     queryType + ": File path not set from launcher app");
493         }
494     }
495 
maybeCreateParentDirInAndroid(File file)496     private void maybeCreateParentDirInAndroid(File file) {
497         final String ownedPathType = getOwnedDirectoryType(file);
498         if (ownedPathType == null) {
499             return;
500         }
501         // Create the external app dir first.
502         if (createExternalAppDir(ownedPathType)) {
503             // Then create everything along the path.
504             file.getParentFile().mkdirs();
505         }
506     }
507 
createExternalAppDir(String name)508     private boolean createExternalAppDir(String name) {
509         // Apps are not allowed to create data/cache/obb etc under Android directly and are
510         // expected to call one of the following methods.
511         switch (name) {
512             case "data":
513                 getApplicationContext().getExternalFilesDirs(null);
514                 getApplicationContext().getExternalCacheDirs();
515                 return true;
516             case "obb":
517                 getApplicationContext().getObbDirs();
518                 return true;
519             case "media":
520                 getApplicationContext().getExternalMediaDirs();
521                 return true;
522             default:
523                 return false;
524         }
525     }
526 
527     /**
528      * Returns null if given path is not an owned path.
529      */
530     @Nullable
getOwnedDirectoryType(File path)531     private static String getOwnedDirectoryType(File path) {
532         final Matcher m = PATTERN_OWNED_PATH.matcher(path.getAbsolutePath());
533         if (m.matches()) {
534             return m.group(1);
535         }
536         return null;
537     }
538 }
539