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 android.annotation.SuppressLint; 20 import android.os.Parcel; 21 import android.os.Parcelable; 22 import android.os.UserHandle; 23 import android.os.storage.StorageVolume; 24 import android.provider.MediaStore; 25 import android.util.Log; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import com.android.modules.utils.build.SdkLevel; 31 32 import java.io.File; 33 import java.util.Objects; 34 35 /** 36 * MediaVolume is a MediaProvider-internal representation of a storage volume. 37 * 38 * Before MediaVolume, volumes inside MediaProvider were represented by their name; 39 * but now that MediaProvider handles volumes on behalf on multiple users, the name of a volume 40 * might no longer be unique. So MediaVolume holds both a name and a user. The user may be 41 * null on volumes without an owner (eg public volumes). 42 * 43 * In addition to that, we keep the path and ID of the volume cached in here as well 44 * for easy access. 45 */ 46 public final class MediaVolume implements Parcelable { 47 /** 48 * Name of the volume. 49 */ 50 private final @NonNull String mName; 51 52 /** 53 * User to which the volume belongs to; might be null in case of public volumes. 54 */ 55 private final @Nullable UserHandle mUser; 56 57 /** 58 * Path on which the volume is mounted. 59 */ 60 private final @Nullable File mPath; 61 62 /** 63 * Unique ID of the volume; eg "external;0" 64 */ 65 private final @Nullable String mId; 66 67 /** 68 * Whether the volume is managed from outside Android. 69 */ 70 private final boolean mExternallyManaged; 71 72 /** 73 * Whether the volume is public. 74 */ 75 private final boolean mPublicVolume; 76 getName()77 public @NonNull String getName() { 78 return mName; 79 } 80 getUser()81 public @Nullable UserHandle getUser() { 82 return mUser; 83 } 84 getPath()85 public @Nullable File getPath() { 86 return mPath; 87 } 88 getId()89 public @Nullable String getId() { 90 return mId; 91 } 92 isExternallyManaged()93 public boolean isExternallyManaged() { 94 return mExternallyManaged; 95 } 96 isPublicVolume()97 public boolean isPublicVolume() { 98 return mPublicVolume; 99 } 100 MediaVolume(@onNull String name, UserHandle user, File path, String id, boolean externallyManaged, boolean mPublicVolume)101 private MediaVolume (@NonNull String name, UserHandle user, File path, String id, 102 boolean externallyManaged, boolean mPublicVolume) { 103 this.mName = name; 104 this.mUser = user; 105 this.mPath = path; 106 this.mId = id; 107 this.mExternallyManaged = externallyManaged; 108 this.mPublicVolume = mPublicVolume; 109 } 110 MediaVolume(Parcel in)111 private MediaVolume (Parcel in) { 112 this.mName = in.readString(); 113 this.mUser = in.readParcelable(null); 114 this.mPath = new File(in.readString()); 115 this.mId = in.readString(); 116 this.mExternallyManaged = in.readInt() != 0; 117 this.mPublicVolume = in.readInt() != 0; 118 } 119 120 @Override equals(Object obj)121 public boolean equals(Object obj) { 122 if (this == obj) return true; 123 if (obj == null || getClass() != obj.getClass()) return false; 124 MediaVolume that = (MediaVolume) obj; 125 // We consciously don't compare the path, because: 126 // 1. On unmount events, the returned path for StorageVolumes is 127 // 'null', and different from a mounted volume. 128 // 2. A volume with a certain ID should never be mounted in two different paths, anyway 129 return Objects.equals(mName, that.mName) && 130 Objects.equals(mUser, that.mUser) && 131 Objects.equals(mId, that.mId) && 132 (mExternallyManaged == that.mExternallyManaged); 133 } 134 135 @Override hashCode()136 public int hashCode() { 137 return Objects.hash(mName, mUser, mId, mExternallyManaged); 138 } 139 isVisibleToUser(UserHandle user)140 public boolean isVisibleToUser(UserHandle user) { 141 return mUser == null || user.equals(mUser); 142 } 143 144 /** 145 * Adding NewApi Suppress Lint to fix some build errors after making 146 * {@link StorageVolume#getOwner()} a public Api 147 */ 148 // TODO(b/213658045) : Remove this once the related changes are submitted. 149 @SuppressLint("NewApi") 150 @NonNull fromStorageVolume(StorageVolume storageVolume)151 public static MediaVolume fromStorageVolume(StorageVolume storageVolume) { 152 String name = storageVolume.getMediaStoreVolumeName(); 153 UserHandle user = storageVolume.getOwner(); 154 File path = storageVolume.getDirectory(); 155 String id = storageVolume.getId(); 156 boolean externallyManaged = 157 SdkLevel.isAtLeastT() ? storageVolume.isExternallyManaged() : false; 158 boolean publicVolume = !externallyManaged && !storageVolume.isPrimary(); 159 return new MediaVolume(name, user, path, id, externallyManaged, publicVolume); 160 } 161 fromInternal()162 public static MediaVolume fromInternal() { 163 String name = MediaStore.VOLUME_INTERNAL; 164 return new MediaVolume(name, null, null, null, false, false); 165 } 166 167 @Override describeContents()168 public int describeContents() { 169 return 0; 170 } 171 172 @Override writeToParcel(Parcel dest, int flags)173 public void writeToParcel(Parcel dest, int flags) { 174 dest.writeString(mName); 175 dest.writeParcelable(mUser, flags); 176 dest.writeString(mPath.toString()); 177 dest.writeString(mId); 178 dest.writeInt(mExternallyManaged ? 1 : 0); 179 dest.writeInt(mPublicVolume ? 1 : 0); 180 } 181 182 @Override toString()183 public String toString() { 184 return "MediaVolume name: [" + mName + "] id: [" + mId + "] user: [" + mUser + "] path: [" 185 + mPath + "] externallyManaged: [" + mExternallyManaged + "] mPublicVolume: [" 186 + mPublicVolume + "]"; 187 } 188 189 public static final @android.annotation.NonNull Creator<MediaVolume> CREATOR 190 = new Creator<MediaVolume>() { 191 @Override 192 public MediaVolume createFromParcel(Parcel in) { 193 return new MediaVolume(in); 194 } 195 196 @Override 197 public MediaVolume[] newArray(int size) { 198 return new MediaVolume[size]; 199 } 200 }; 201 } 202