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