• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import static com.android.providers.media.util.Logging.TAG;
20 
21 import android.content.Context;
22 import android.net.Uri;
23 import android.os.UserHandle;
24 import android.os.UserManager;
25 import android.os.storage.StorageManager;
26 import android.os.storage.StorageVolume;
27 import android.provider.MediaStore;
28 import android.util.ArrayMap;
29 import android.util.ArraySet;
30 import android.util.Log;
31 
32 import androidx.annotation.GuardedBy;
33 import androidx.annotation.NonNull;
34 
35 import com.android.providers.media.util.FileUtils;
36 import com.android.providers.media.util.UserCache;
37 import java.io.File;
38 import java.io.FileNotFoundException;
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 
46 /**
47  * The VolumeCache class keeps track of all the volumes that are available,
48  * as well as their scan paths.
49  */
50 public class VolumeCache {
51     private final Context mContext;
52 
53     private final Object mLock = new Object();
54 
55     private final UserManager mUserManager;
56     private final UserCache mUserCache;
57 
58     @GuardedBy("mLock")
59     private final ArrayList<MediaVolume> mExternalVolumes = new ArrayList<>();
60 
61     @GuardedBy("mLock")
62     private final Map<MediaVolume, Collection<File>> mCachedVolumeScanPaths = new ArrayMap<>();
63 
64     @GuardedBy("mLock")
65     private Collection<File> mCachedInternalScanPaths;
66 
VolumeCache(Context context, UserCache userCache)67     public VolumeCache(Context context, UserCache userCache) {
68         mContext = context;
69         mUserManager = context.getSystemService(UserManager.class);
70         mUserCache = userCache;
71     }
72 
getExternalVolumes()73     public @NonNull List<MediaVolume> getExternalVolumes() {
74         synchronized(mLock) {
75             return new ArrayList<>(mExternalVolumes);
76         }
77     }
78 
getExternalVolumeNames()79     public @NonNull Set<String> getExternalVolumeNames() {
80         synchronized (mLock) {
81             ArraySet<String> volNames = new ArraySet<String>();
82             for (MediaVolume vol : mExternalVolumes) {
83                 volNames.add(vol.getName());
84             }
85             return volNames;
86         }
87     }
88 
89     /**
90      * @return List of paths to unreliable volumes if any, an empty list otherwise
91      */
getUnreliableVolumePath()92     public @NonNull List<File> getUnreliableVolumePath() throws FileNotFoundException {
93         List<File> unreliableVolumes = new ArrayList<>();
94         synchronized (mLock) {
95             for (MediaVolume volume : mExternalVolumes){
96                 final File volPath = volume.getPath();
97                 if (volPath != null && volPath.getPath() != null
98                         && !volPath.getPath().startsWith("/storage/")){
99                     unreliableVolumes.add(volPath);
100                 }
101             }
102         }
103 
104         return unreliableVolumes;
105     }
106 
findVolume(@onNull String volumeName, @NonNull UserHandle user)107     public @NonNull MediaVolume findVolume(@NonNull String volumeName, @NonNull UserHandle user)
108             throws FileNotFoundException {
109         synchronized (mLock) {
110             for (MediaVolume vol : mExternalVolumes) {
111                 if (vol.getName().equals(volumeName) && vol.isVisibleToUser(user)) {
112                     return vol;
113                 }
114             }
115         }
116 
117         throw new FileNotFoundException("Couldn't find volume with name " + volumeName);
118     }
119 
getVolumePath(@onNull String volumeName, @NonNull UserHandle user)120     public @NonNull File getVolumePath(@NonNull String volumeName, @NonNull UserHandle user)
121             throws FileNotFoundException {
122         synchronized (mLock) {
123             try {
124                 MediaVolume volume = findVolume(volumeName, user);
125                 return volume.getPath();
126             } catch (FileNotFoundException e) {
127                 Log.w(TAG, "getVolumePath for unknown volume: " + volumeName);
128                 // Try again by using FileUtils below
129             }
130 
131             final Context userContext = mUserCache.getContextForUser(user);
132             return FileUtils.getVolumePath(userContext, volumeName);
133         }
134     }
135 
getVolumeScanPaths(@onNull String volumeName, @NonNull UserHandle user)136     public @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName,
137             @NonNull UserHandle user) throws FileNotFoundException {
138         synchronized (mLock) {
139             if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
140                 // Internal is shared by all users
141                 return mCachedInternalScanPaths;
142             }
143             try {
144                 MediaVolume volume = findVolume(volumeName, user);
145                 if (mCachedVolumeScanPaths.containsKey(volume)) {
146                     return mCachedVolumeScanPaths.get(volume);
147                 }
148             } catch (FileNotFoundException e) {
149                 Log.w(TAG, "Didn't find cached volume scan paths for " + volumeName);
150             }
151 
152             // Nothing found above; let's ask directly
153             final Context userContext = mUserCache.getContextForUser(user);
154             final Collection<File> res = FileUtils.getVolumeScanPaths(userContext, volumeName);
155 
156             return res;
157         }
158     }
159 
findVolumeForFile(@onNull File file)160     public @NonNull MediaVolume findVolumeForFile(@NonNull File file) throws FileNotFoundException {
161         synchronized (mLock) {
162             for (MediaVolume volume : mExternalVolumes) {
163                 if (FileUtils.contains(volume.getPath(), file)) {
164                     return volume;
165                 }
166             }
167         }
168 
169         Log.w(TAG, "Didn't find any volume for getVolume(" + file.getPath() + ")");
170         // Nothing found above; let's ask directly
171         final StorageManager sm = mContext.getSystemService(StorageManager.class);
172         final StorageVolume volume = sm.getStorageVolume(file);
173         if (volume == null) {
174             throw new FileNotFoundException("Missing volume for " + file);
175         }
176 
177         return MediaVolume.fromStorageVolume(volume);
178     }
179 
getVolumeId(@onNull File file)180     public @NonNull String getVolumeId(@NonNull File file) throws FileNotFoundException {
181         MediaVolume volume = findVolumeForFile(file);
182 
183         return volume.getId();
184     }
185 
186     @GuardedBy("mLock")
updateExternalVolumesForUserLocked(Context userContext)187     private void updateExternalVolumesForUserLocked(Context userContext) {
188         final StorageManager sm = userContext.getSystemService(StorageManager.class);
189         for (String volumeName : MediaStore.getExternalVolumeNames(userContext)) {
190             try {
191                 final Uri uri = MediaStore.Files.getContentUri(volumeName);
192                 final StorageVolume storageVolume = sm.getStorageVolume(uri);
193                 MediaVolume volume = MediaVolume.fromStorageVolume(storageVolume);
194                 mExternalVolumes.add(volume);
195                 mCachedVolumeScanPaths.put(volume, FileUtils.getVolumeScanPaths(userContext,
196                             volume.getName()));
197             } catch (IllegalStateException | FileNotFoundException e) {
198                 Log.wtf(TAG, "Failed to update volume " + volumeName, e);
199             }
200         }
201     }
202 
update()203     public void update() {
204         synchronized (mLock) {
205             mCachedVolumeScanPaths.clear();
206             try {
207                 mCachedInternalScanPaths = FileUtils.getVolumeScanPaths(mContext,
208                         MediaStore.VOLUME_INTERNAL);
209             } catch (FileNotFoundException e) {
210                 Log.wtf(TAG, "Failed to update volume " + MediaStore.VOLUME_INTERNAL,e );
211             }
212             mExternalVolumes.clear();
213             List<UserHandle> users = mUserCache.updateAndGetUsers();
214             for (UserHandle user : users) {
215                 Context userContext = mUserCache.getContextForUser(user);
216                 updateExternalVolumesForUserLocked(userContext);
217             }
218         }
219     }
220 
dump(PrintWriter writer)221     public void dump(PrintWriter writer) {
222         writer.println("Volume cache state:");
223         synchronized (mLock) {
224             for (MediaVolume volume : mExternalVolumes)  {
225                 writer.println("  " + volume.toString());
226             }
227         }
228     }
229 }
230