1 /*
2  * Copyright (C) 2021 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.providers.media.util;
18 
19 import static com.android.providers.media.PickerUriResolver.PICKER_GET_CONTENT_SEGMENT;
20 import static com.android.providers.media.PickerUriResolver.PICKER_SEGMENT;
21 import static com.android.providers.media.util.FileUtils.buildPath;
22 import static com.android.providers.media.util.FileUtils.buildPrimaryVolumeFile;
23 import static com.android.providers.media.util.FileUtils.extractFileName;
24 
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.annotation.VisibleForTesting;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.RandomAccessFile;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Locale;
36 
37 public final class SyntheticPathUtils {
38     private static final String TAG = "SyntheticPathUtils";
39 
40     private static final String TRANSFORMS_DIR = ".transforms";
41     private static final String SYNTHETIC_DIR = "synthetic";
42     private static final String REDACTED_DIR = "redacted";
43 
44     public static final String REDACTED_URI_ID_PREFIX = "RUID";
45     public static final int REDACTED_URI_ID_SIZE = 36;
46 
SyntheticPathUtils()47     private SyntheticPathUtils() {}
48 
getRedactedRelativePath()49     public static String getRedactedRelativePath() {
50         return buildPath(/* base */ null, TRANSFORMS_DIR, SYNTHETIC_DIR, REDACTED_DIR).getPath();
51     }
52 
53     /**
54      * Returns picker synthetic path directory.
55      */
getPickerRelativePath(String pickerSegmentType)56     public static String getPickerRelativePath(String pickerSegmentType) {
57         return buildPath(/* base */ null, TRANSFORMS_DIR, SYNTHETIC_DIR,
58                 pickerSegmentType).getPath();
59     }
60 
isRedactedPath(String path, int userId)61     public static boolean isRedactedPath(String path, int userId) {
62         if (path == null) return false;
63 
64         final String redactedDir = buildPrimaryVolumeFile(userId, getRedactedRelativePath())
65                 .getAbsolutePath();
66         final String fileName = extractFileName(path);
67 
68         return fileName != null
69                 && startsWith(path, redactedDir)
70                 && startsWith(fileName, REDACTED_URI_ID_PREFIX)
71                 && fileName.length() == REDACTED_URI_ID_SIZE;
72     }
73 
isPickerPath(String path, int userId)74     public static boolean isPickerPath(String path, int userId) {
75         final String pickerDir = buildPrimaryVolumeFile(userId, getPickerRelativePath(
76                 PICKER_SEGMENT)).getAbsolutePath();
77         final String pickerGetContentDir = buildPrimaryVolumeFile(userId,
78                 getPickerRelativePath(PICKER_GET_CONTENT_SEGMENT)).getAbsolutePath();
79 
80         return path != null && (startsWith(path, pickerDir) || startsWith(path,
81                 pickerGetContentDir));
82     }
83 
isSyntheticPath(String path, int userId)84     public static boolean isSyntheticPath(String path, int userId) {
85         final String syntheticDir = buildPrimaryVolumeFile(userId, getSyntheticRelativePath())
86                 .getAbsolutePath();
87 
88         return path != null && startsWith(path, syntheticDir);
89     }
90 
extractSyntheticRelativePathSegements(String path, int userId)91     public static List<String> extractSyntheticRelativePathSegements(String path, int userId) {
92         final List<String> segments = new ArrayList<>();
93         final String syntheticDir = buildPrimaryVolumeFile(userId,
94                 getSyntheticRelativePath()).getAbsolutePath();
95 
96         if (path.toLowerCase(Locale.ROOT).indexOf(syntheticDir.toLowerCase(Locale.ROOT)) < 0) {
97             return segments;
98         }
99 
100         final String[] segmentArray = path.substring(syntheticDir.length()).split("/");
101         for (String segment : segmentArray) {
102             if (TextUtils.isEmpty(segment)) {
103                 continue;
104             }
105             segments.add(segment);
106         }
107 
108         return segments;
109     }
110 
createSparseFile(File file, long size)111     public static boolean createSparseFile(File file, long size) {
112         if (size < 0) {
113             return false;
114         }
115 
116         try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
117             raf.setLength(size);
118             return true;
119         } catch (IOException e) {
120             Log.e(TAG, "Failed to create sparse file: " + file, e);
121             file.delete();
122             return false;
123         }
124     }
125 
126     @VisibleForTesting
getSyntheticRelativePath()127     static String getSyntheticRelativePath() {
128         return buildPath(/* base */ null, TRANSFORMS_DIR, SYNTHETIC_DIR).getPath();
129     }
130 
startsWith(String value, String prefix)131     private static boolean startsWith(String value, String prefix) {
132         return value.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT));
133     }
134 }
135