Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 90243296 authored by Martijn Coenen's avatar Martijn Coenen
Browse files

Create WatchableVolumeInfo object.

In preparation of caching the getVolumeList() API, create a new
WatchableVolumeInfo object that wraps VolumeInfo. Unfortunately it is
not possible to safely make VolumeInfo itself Watchable, because it
exposes public members that are also tagged @UnsupportedAppUsage, which
means we cannot safely change their visibility. This allows VolumeInfo
objects being modified without notifying the watchers. Since this can
lead to very obscure bugs when caching volumes, instead wrap VolumeInfo
in a new WatchableVolumeInfo class, which does safely implement
Watchable. Change StorageManagerService to use it; the only place where
we still allow a WatchableVolumeInfo to be converted into a VolumeInfo
object is when return a list of volumes to other processes, in
getVolumeList() itself.

Bug: 323574186
Test: N/A, refactoring code
Flag: EXEMPT refactoring

Change-Id: Idda040f28df2e3656014d710280706ccfbc6158c
parent 93d7e93f
Loading
Loading
Loading
Loading
+122 −116

File changed.

Preview size limit exceeded, changes collapsed.

+8 −7
Original line number Original line Diff line number Diff line
@@ -81,16 +81,16 @@ public final class StorageSessionController {
     */
     */
    public int getConnectionUserIdForVolume(VolumeInfo vol) {
    public int getConnectionUserIdForVolume(VolumeInfo vol) {
        final Context volumeUserContext = mContext.createContextAsUser(
        final Context volumeUserContext = mContext.createContextAsUser(
                UserHandle.of(vol.mountUserId), 0);
                UserHandle.of(vol.getMountUserId()), 0);
        boolean isMediaSharedWithParent = volumeUserContext.getSystemService(
        boolean isMediaSharedWithParent = volumeUserContext.getSystemService(
                UserManager.class).isMediaSharedWithParent();
                UserManager.class).isMediaSharedWithParent();


        UserInfo userInfo = mUserManager.getUserInfo(vol.mountUserId);
        UserInfo userInfo = mUserManager.getUserInfo(vol.getMountUserId());
        if (userInfo != null && isMediaSharedWithParent) {
        if (userInfo != null && isMediaSharedWithParent) {
            // Clones use the same connection as their parent
            // Clones use the same connection as their parent
            return userInfo.profileGroupId;
            return userInfo.profileGroupId;
        } else {
        } else {
            return vol.mountUserId;
            return vol.getMountUserId();
        }
        }
    }
    }


@@ -144,7 +144,8 @@ public final class StorageSessionController {
     *
     *
     * @throws ExternalStorageServiceException if it fails to connect to ExternalStorageService
     * @throws ExternalStorageServiceException if it fails to connect to ExternalStorageService
     */
     */
    public void notifyVolumeStateChanged(VolumeInfo vol) throws ExternalStorageServiceException {
    public void notifyVolumeStateChanged(VolumeInfo vol)
            throws ExternalStorageServiceException {
        if (!shouldHandle(vol)) {
        if (!shouldHandle(vol)) {
            return;
            return;
        }
        }
@@ -458,8 +459,8 @@ public final class StorageSessionController {
     * {@code false} otherwise
     * {@code false} otherwise
     **/
     **/
    public static boolean isEmulatedOrPublic(VolumeInfo vol) {
    public static boolean isEmulatedOrPublic(VolumeInfo vol) {
        return vol.type == VolumeInfo.TYPE_EMULATED
        return vol.getType() == VolumeInfo.TYPE_EMULATED
                || (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isVisible());
                || (vol.getType() == VolumeInfo.TYPE_PUBLIC && vol.isVisible());
    }
    }


    /** Exception thrown when communication with the {@link ExternalStorageService} fails. */
    /** Exception thrown when communication with the {@link ExternalStorageService} fails. */
@@ -478,7 +479,7 @@ public final class StorageSessionController {
    }
    }


    private static boolean isSupportedVolume(VolumeInfo vol) {
    private static boolean isSupportedVolume(VolumeInfo vol) {
        return isEmulatedOrPublic(vol) || vol.type == VolumeInfo.TYPE_STUB;
        return isEmulatedOrPublic(vol) || vol.getType() == VolumeInfo.TYPE_STUB;
    }
    }


    private boolean shouldHandle(@Nullable VolumeInfo vol) {
    private boolean shouldHandle(@Nullable VolumeInfo vol) {
+202 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.storage;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.storage.DiskInfo;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;

import com.android.internal.util.IndentingPrintWriter;
import com.android.server.utils.Watchable;
import com.android.server.utils.WatchableImpl;

import java.io.File;

/**
 * A wrapper for {@link VolumeInfo}  implementing the {@link Watchable} interface.
 *
 * The {@link VolumeInfo} class itself cannot safely implement Watchable, because it has several
 * UnsupportedAppUsage annotations and public fields, which allow it to be modified without
 * notifying watchers.
 *
 * @hide
 */
public class WatchedVolumeInfo extends WatchableImpl {
    private final VolumeInfo mVolumeInfo;

    private WatchedVolumeInfo(VolumeInfo volumeInfo) {
        mVolumeInfo = volumeInfo;
    }

    public WatchedVolumeInfo(WatchedVolumeInfo watchedVolumeInfo) {
        mVolumeInfo = new VolumeInfo(watchedVolumeInfo.mVolumeInfo);
    }

    public static WatchedVolumeInfo fromVolumeInfo(VolumeInfo info) {
        return new WatchedVolumeInfo(info);
    }

    /**
     * Returns a copy of the embedded VolumeInfo object, to be used by components
     * that just need it for retrieving some state from it.
     *
     * @return A copy of the embedded VolumeInfo object
     */

    public WatchedVolumeInfo clone() {
        return fromVolumeInfo(mVolumeInfo.clone());
    }

    public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
        return mVolumeInfo.buildStorageVolume(context, userId, reportUnmounted);
    }

    public void dump(IndentingPrintWriter pw) {
        mVolumeInfo.dump(pw);
    }

    public DiskInfo getDisk() {
        return mVolumeInfo.getDisk();
    }

    public String getDiskId() {
        return mVolumeInfo.getDiskId();
    }

    public String getFsLabel() {
        return mVolumeInfo.fsLabel;
    }

    public void setFsLabel(String fsLabel) {
        mVolumeInfo.fsLabel = fsLabel;
        dispatchChange(this);
    }

    public String getFsPath() {
        return mVolumeInfo.path;
    }

    public void setFsPath(String path) {
        mVolumeInfo.path = path;
        dispatchChange(this);
    }

    public String getFsType() {
        return mVolumeInfo.fsType;
    }

    public void setFsType(String fsType) {
        mVolumeInfo.fsType = fsType;
        dispatchChange(this);
    }

    public @Nullable String getFsUuid() {
        return mVolumeInfo.fsUuid;
    }

    public void setFsUuid(String fsUuid) {
        mVolumeInfo.fsUuid = fsUuid;
        dispatchChange(this);
    }

    public @NonNull String getId() {
        return mVolumeInfo.id;
    }

    public File getInternalPath() {
        return mVolumeInfo.getInternalPath();
    }

    public void setInternalPath(String internalPath) {
        mVolumeInfo.internalPath = internalPath;
        dispatchChange(this);
    }

    public int getMountFlags() {
        return mVolumeInfo.mountFlags;
    }

    public void setMountFlags(int mountFlags) {
        mVolumeInfo.mountFlags = mountFlags;
        dispatchChange(this);
    }

    public int getMountUserId() {
        return mVolumeInfo.mountUserId;
    }

    public void setMountUserId(int mountUserId) {
        mVolumeInfo.mountUserId = mountUserId;
        dispatchChange(this);
    }

    public String getPartGuid() {
        return mVolumeInfo.partGuid;
    }

    public File getPath() {
        return mVolumeInfo.getPath();
    }

    public int getState() {
        return mVolumeInfo.state;
    }

    public int getState(int state) {
        return mVolumeInfo.state;
    }

    public void setState(int state) {
        mVolumeInfo.state = state;
        dispatchChange(this);
    }

    public int getType() {
        return mVolumeInfo.type;
    }

    public VolumeInfo getVolumeInfo() {
        return new VolumeInfo(mVolumeInfo);
    }

    public boolean isMountedReadable() {
        return mVolumeInfo.isMountedReadable();
    }

    public boolean isMountedWritable() {
        return mVolumeInfo.isMountedWritable();
    }

    public boolean isPrimary() {
        return mVolumeInfo.isPrimary();
    }

    public boolean isVisible() {
        return mVolumeInfo.isVisible();
    }

    public boolean isVisibleForUser(int userId) {
        return mVolumeInfo.isVisibleForUser(userId);
    }

    public boolean isVisibleForWrite(int userId) {
        return mVolumeInfo.isVisibleForWrite(userId);
    }
}
 No newline at end of file