SD卡迁移数据流程 & 迁移数据失败

一. 简介

格式化为内部存储是android 6.0引进了adoptable device的概念,意思是可以把外置SD 卡格式化为内卡使用

整个操作过程是:format as internal--migrate data后外卡就变为内卡使用

和普通的格式化区别在于:相当于内置SD卡的扩容,此时的外置SD卡会变成和原内部存储一样的地位,迁移数据也是把原内部存储数据 转到sdcard 中

Google对这个功能的说明如下:

https://source.android.com/devices/storage/adoptable

二. 流程   

1. 代码流程

(1). 根据视频中的UI,找到需要点击的按钮是:移动内容.

     找到定义字符串:

     vendor/mediatek/proprietary/packages/apps/MtkSettings/res/values-zh-rCN/strings.xml

    "移动内容"

 (2). 找到此字符串的引用

vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      ...
        setNextButtonText(R.string.storage_wizard_migrate_v2_now);
    }

 (3). 找到setNextButtonText 函数的定义

/vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/deviceinfo/StorageWizardBase.java

    protected void setNextButtonText(int resId, CharSequence... args) {
        mNext.setText(TextUtils.expandTemplate(getText(resId), args));
        mNext.setVisibility(View.VISIBLE);
    }

(4).  mNext 的定义:

        mFooterBarMixin = getGlifLayout().getMixin(FooterBarMixin.class);

        mFooterBarMixin.setPrimaryButton(
            new FooterButton.Builder(this)
                .setText(R.string.wizard_next)

                //对应的回调是onNavigateNext函数
                .setListener(this::onNavigateNext)
                .setButtonType(FooterButton.ButtonType.NEXT)
                .setTheme(R.style.SudGlifButton_Primary)
                .build()
        );
        mNext = mFooterBarMixin.getPrimaryButton();

(5). onNavigateNext 的实现

packages/apps/Settings/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java

   @Override
    public void onNavigateNext(View view) {
      ...

        // We only expect exceptions from StorageManagerService#setPrimaryStorageUuid
        int moveId;
        try {

            //调用迁移数据movePrimaryStorage的函数
            moveId = getPackageManager().movePrimaryStorage(mVolume);
        } catch (IllegalArgumentException e) {
            StorageManager sm = (StorageManager) getSystemService(STORAGE_SERVICE);

            if (Objects.equals(mVolume.getFsUuid(), sm.getPrimaryStorageVolume().getUuid())) {
                final Intent intent = new Intent(this, StorageWizardReady.class);
                intent.putExtra(DiskInfo.EXTRA_DISK_ID,
                        getIntent().getStringExtra(DiskInfo.EXTRA_DISK_ID));
                startActivity(intent);
                finishAffinity();

                return;
            } else {
                throw e;
            }
        } catch (IllegalStateException e) {
            Toast.makeText(this, getString(R.string.another_migration_already_in_progress),
                    Toast.LENGTH_LONG).show();
            finishAffinity();

            return;
        }

      ...
    }
 

(6).  movePrimaryStorage 函数

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

        @Override
        public int movePrimaryStorage(String volumeUuid) throws RemoteException {
            mContext.enforceCallingOrSelfPermission(Manifest.permission.MOVE_PACKAGE, null);

            final int realMoveId = mNextMoveId.getAndIncrement();
            final Bundle extras = new Bundle();
            extras.putString(VolumeRecord.EXTRA_FS_UUID, volumeUuid);
            mMoveCallbacks.notifyCreated(realMoveId, extras);

            final IPackageMoveObserver callback = new IPackageMoveObserver.Stub() {
                @Override
                public void onCreated(int moveId, Bundle extras) {
                    // Ignored
                }

                @Override
                public void onStatusChanged(int moveId, int status, long estMillis) {
                    mMoveCallbacks.notifyStatusChanged(realMoveId, status, estMillis);
                }
            };

            final StorageManager storage = mInjector.getSystemService(StorageManager.class);

           //通过设置Primary Storage 真正的数据迁移
            storage.setPrimaryStorageUuid(volumeUuid, callback);
            return realMoveId;
        }

(7). setPrimaryStorageUuid 函数

frameworks/base/services/core/java/com/android/server/StorageManagerService.java

    @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)
    @Override
    public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {

        super.setPrimaryStorageUuid_enforcePermission();

        final VolumeInfo from;
        final VolumeInfo to;

        synchronized (mLock) {
            if (Objects.equals(mPrimaryStorageUuid, volumeUuid)) {
                throw new IllegalArgumentException("Primary storage already at " + volumeUuid);
            }

            if (mMoveCallback != null) {
                throw new IllegalStateException("Move already in progress");
            }
            mMoveCallback = callback;
            mMoveTargetUuid = volumeUuid;

            // We need all the users unlocked to move their primary storage
            final List users = mContext.getSystemService(UserManager.class).getUsers();
            for (UserInfo user : users) {
                if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) {
                    Slog.w(TAG, "Failing move due to locked user " + user.id);
                    onMoveStatusLocked(PackageManager.MOVE_FAILED_LOCKED_USER);
                    return;
                }
            }

            // When moving to/from primary physical volume, we probably just nuked
            // the current storage location, so we have nothing to move.
            if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)
                    || Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
                Slog.d(TAG, "Skipping move to/from primary physical");
                onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
                onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
                mHandler.obtainMessage(H_RESET).sendToTarget();
                return;

            } else {
                int currentUserId = mCurrentUserId;
                from = findStorageForUuidAsUser(mPrimaryStorageUuid, currentUserId);
                to = findStorageForUuidAsUser(volumeUuid, currentUserId);

                if (from == null) {
                    Slog.w(TAG, "Failing move due to missing from volume " + mPrimaryStorageUuid);
                    onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
                    return;
                } else if (to == null) {
                    Slog.w(TAG, "Failing move due to missing to volume " + volumeUuid);
                    onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
                    return;
                }
            }
        }

        try {
            mVold.moveStorage(from.id, to.id, new IVoldTaskListener.Stub() {
                @Override
                public void onStatus(int status, PersistableBundle extras) {
                    synchronized (mLock) {
                        onMoveStatusLocked(status);
                    }
                }

                @Override
                public void onFinished(int status, PersistableBundle extras) {
                    // Not currently used
                }
            });
        } catch (Exception e) {
            Slog.wtf(TAG, e);
        }
    }

(8). moveStorage 函数

system/vold/VoldNativeService.cpp

binder::Status VoldNativeService::moveStorage(
        const std::string& fromVolId, const std::string& toVolId,
        const android::sp& listener) {
    ENFORCE_SYSTEM_OR_ROOT;
    CHECK_ARGUMENT_ID(fromVolId);
    CHECK_ARGUMENT_ID(toVolId);
    ACQUIRE_LOCK;

    auto fromVol = VolumeManager::Instance()->findVolume(fromVolId);
    auto toVol = VolumeManager::Instance()->findVolume(toVolId);
    if (fromVol == nullptr) {
        return error("Failed to find volume " + fromVolId);
    } else if (toVol == nullptr) {
        return error("Failed to find volume " + toVolId);
    }

    std::thread([=]() { android::vold::MoveStorage(fromVol, toVol, listener); }).detach();
    return Ok();
}

 (9). MoveStorage 函数

system/vold/MoveStorage.cpp

void MoveStorage(const std::shared_ptr& from, const std::shared_ptr& to,
                 const android::sp& listener) {
    auto wl = android::wakelock::WakeLock::tryGet(kWakeLock);
    if (!wl.has_value()) {
        return;
    }

    android::os::PersistableBundle extras;
    status_t res = moveStorageInternal(from, to, listener);
    if (listener) {

       //告知StorageManagerService::onFinished
        listener->onFinished(res, extras);
    }
}

(10) moveStorageInternal 函数:

static status_t moveStorageInternal(const std::shared_ptr& from,
                                    const std::shared_ptr& to,
                                    const android::sp& listener) {
    std::string fromPath;
    std::string toPath;

    // TODO: add support for public volumes

     //支持迁移数据的type都是kEmulated
    if (from->getType() != VolumeBase::Type::kEmulated) goto fail;
    if (to->getType() != VolumeBase::Type::kEmulated) goto fail;

    // Step 1: tear down volumes and mount silently without making

   //第一步: 拆除卷并静默挂载,而不会让用户空间应用程序看到
    // visible to userspace apps
    {
        std::lock_guard lock(VolumeManager::Instance()->getLock());
        bringOffline(from);
        bringOffline(to);
    }

    fromPath = from->getInternalPath();
    toPath = to->getInternalPath();

    // Step 2: clean up any stale data

    //第二步: 清理任何陈旧的数据表数据
    if (execRm(toPath, 10, 10, listener) != OK) {
        goto fail;
    }

    // Step 3: perform actual copy

    //第三步: 执行数据复制
    if (execCp(fromPath, toPath, 20, 60, listener) != OK) {
        goto copy_fail;
    }

    // NOTE: MountService watches for this magic value to know
    // that move was successful

     //通知java 层: StorageManagerService.java ::onStatus
    notifyProgress(82, listener);
    {
        std::lock_guard lock(VolumeManager::Instance()->getLock());
        bringOnline(from);
        bringOnline(to);
    }

    // Step 4: clean up old data

    //删除旧数据
    if (execRm(fromPath, 85, 15, listener) != OK) {
        goto fail;
    }

    //通知java 层: StorageManagerService.java ::onStatus

    notifyProgress(kMoveSucceeded, listener);
    return OK;

copy_fail:
    // if we failed to copy the data we should not leave it laying around
    // in target location. Do not check return value, we can not do any
    // useful anyway.

     //针对copy fail
    execRm(toPath, 80, 1, listener);
fail:
    // clang-format off
    {
        std::lock_guard lock(VolumeManager::Instance()->getLock());

       // from 和 to 状态重置
        bringOnline(from);
        bringOnline(to);
    }
    // clang-format on

    // //通知java 层: StorageManagerService.java ::onStatus
    notifyProgress(kMoveFailedInternalError, listener);
    return -1;
}

(11). execCp 函数

static status_t execCp(const std::string& fromPath, const std::string& toPath, int startProgress,
                       int stepProgress,
                       const android::sp& listener) {
    notifyProgress(startProgress, listener);

    uint64_t expectedBytes = GetTreeBytes(fromPath);
    uint64_t startFreeBytes = GetFreeBytes(toPath);

    if (expectedBytes > startFreeBytes) {
        LOG(ERROR) << "Data size " << expectedBytes << " is too large to fit in free space "
                   << startFreeBytes;
        return -1;
    }

    std::vector cmd;

   //kCpPath 定义: static const char* kCpPath = "/system/bin/cp";
    cmd.push_back(kCpPath);
    cmd.push_back("-p"); /* preserve timestamps, ownership, and permissions */
    cmd.push_back("-R"); /* recurse into subdirectories (DEST must be a directory) */
    cmd.push_back("-P"); /* Do not follow symlinks [default] */
    cmd.push_back("-d"); /* don't dereference symlinks */
    if (!pushBackContents(fromPath, cmd, 1)) {
        LOG(WARNING) << "No contents in " << fromPath;
        return OK;
    }
    cmd.push_back(toPath.c_str());

    if (android::base::GetBoolProperty(kPropBlockingExec, false)) {
        return ForkExecvp(cmd);
    }

    pid_t pid = ForkExecvpAsync(cmd);
    if (pid == -1) return -1;

    int status;
    while (true) {
        if (waitpid(pid, &status, WNOHANG) == pid) {
            if (WIFEXITED(status)) {
                LOG(DEBUG) << "Finished cp with status " << WEXITSTATUS(status);
                return (WEXITSTATUS(status) == 0) ? OK : -1;
            } else {
                break;
            }
        }

        sleep(1);
        uint64_t deltaFreeBytes = startFreeBytes - GetFreeBytes(toPath);
        notifyProgress(
            startProgress +
                CONSTRAIN((int)((deltaFreeBytes * stepProgress) / expectedBytes), 0, stepProgress),
            listener);
    }
    return -1;
}

2. 其他相关函数

(1) execRm 函数

static status_t execRm(const std::string& path, int startProgress, int stepProgress,
                       const android::sp& listener) {
    notifyProgress(startProgress, listener);

    uint64_t expectedBytes = GetTreeBytes(path);
    uint64_t startFreeBytes = GetFreeBytes(path);

    std::vector cmd;

   // kRmPath 定义: static const char* kRmPath = "/system/bin/rm";
    cmd.push_back(kRmPath);
    cmd.push_back("-f"); /* force: remove without confirmation, no error if it doesn't exist */
    cmd.push_back("-R"); /* recursive: remove directory contents */
    if (!pushBackContents(path, cmd, 2)) {
        LOG(WARNING) << "No contents in " << path;
        return OK;
    }

    if (android::base::GetBoolProperty(kPropBlockingExec, false)) {
        return ForkExecvp(cmd);
    }

    pid_t pid = ForkExecvpAsync(cmd);
    if (pid == -1) return -1;

    int status;
    while (true) {
        if (waitpid(pid, &status, WNOHANG) == pid) {
            if (WIFEXITED(status)) {
                LOG(DEBUG) << "Finished rm with status " << WEXITSTATUS(status);
                return (WEXITSTATUS(status) == 0) ? OK : -1;
            } else {
                break;
            }
        }

        sleep(1);
        uint64_t deltaFreeBytes = GetFreeBytes(path) - startFreeBytes;
        notifyProgress(
            startProgress +
                CONSTRAIN((int)((deltaFreeBytes * stepProgress) / expectedBytes), 0, stepProgress),
            listener);
    }
    return -1;
}

(2) ForkExecvpAsync 函数:

system/vold/Utils.cpp

pid_t ForkExecvpAsync(const std::vector& args, char* context) {
    auto argv = ConvertToArgv(args);

    pid_t pid = fork();
    if (pid == 0) {
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
        if (context) {
            if (setexeccon(context)) {
                LOG(ERROR) << "Failed to setexeccon in ForkExecvpAsync";
                abort();
            }
        }

        //运行命令,argv[0] 表示放入argv首指针

        execvp(argv[0], const_cast(argv.data()));
        PLOG(ERROR) << "exec in ForkExecvpAsync";
        _exit(EXIT_FAILURE);
    }
    if (pid == -1) {
        PLOG(ERROR) << "fork in ForkExecvpAsync";
        return -1;
    }
    return pid;
}

(3) ConvertToArgv 函数:

 static std::vector ConvertToArgv(const std::vector& args) {
    std::vector argv;
    argv.reserve(args.size() + 1);
    for (const auto& arg : args) {
        if (argv.empty()) {
            LOG(DEBUG) << arg;
        } else {
            LOG(DEBUG) << "    " << arg;
        }
        argv.emplace_back(arg.data());
    }
    argv.emplace_back(nullptr);
    return argv;
}

3.  流程汇总

StorageWizardMigrateConfirm.java::onNavigateNext
---> PackageManagerService.java::movePrimaryStorage
---> StorageManagerService.java::setPrimaryStorageUuid
---> VoldNativeService.cpp ::moveStorage
---> MoveStorage.cpp::MoveStorage
---> MoveStorage.cpp::moveStorageInternal
   //第一步: 拆除卷并静默挂载,而不会让用户空间应用程序看到
    //第二步: 清理任何陈旧的数据表数据
    //第三步: 执行数据复制
    //第四步:删除旧数据
    //第五步:通知java上层

三. 案例

1.描述

【前提条件】:已插入SD卡
【操作步骤】:
1.设置-存储-SD卡,格式化SD卡为内部存储
2.设置-存储-SD卡,进行SD卡迁移数据
【实际结果】:
出现SD卡无法进行迁移数据,提示存储空间不够无法移动内容,见视频
【预期结果】:概览界面不能点击使用YouTube应用

迁移数据失败

2. 日志分析

从log看data数据要大于sdcard free空间,所以迁移失败提示存储空间不足.

10-20 18:48:16.398084   494 29195 E vold    : Data size 31905890304 is too large to fit in free space 31423475712

10-20 18:48:35.339858   494 29247 E vold    : Data size 31905890304 is too large to fit in free space 31423475712

3. 测试验证

   1. 删除data磁盘里面数据.

   2. 换一个更大的磁盘.

    两种测试手法,都可以验证成功.

你可能感兴趣的:(storage,framework,android)