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