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