blob: c9a3e1766f96507059ac8a74a8b370f62a8c230a [file] [log] [blame]
/*
* Copyright (C) 2015 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.tv.settings.device.storage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.leanback.app.GuidedStepSupportFragment;
import androidx.leanback.widget.GuidanceStylist;
import androidx.leanback.widget.GuidedAction;
import com.android.tv.settings.R;
import com.android.tv.settings.dialog.ProgressDialogFragment;
import java.io.File;
import java.util.List;
import java.util.Objects;
public class MigrateStorageActivity extends FragmentActivity {
private static final String TAG = "MigrateStorageActivity";
private static final String EXTRA_MIGRATE_HERE =
"com.android.tv.settings.device.storage.MigrateStorageActivity.MIGRATE_HERE";
private static final String SAVE_STATE_MOVE_ID = "MigrateStorageActivity.MOVE_ID";
private VolumeInfo mTargetVolumeInfo;
private VolumeInfo mVolumeInfo;
private String mTargetVolumeDesc;
private String mVolumeDesc;
private int mMoveId = -1;
private final Handler mHandler = new Handler();
private PackageManager mPackageManager;
private final PackageManager.MoveCallback mMoveCallback = new PackageManager.MoveCallback() {
@Override
public void onStatusChanged(int moveId, int status, long estMillis) {
if (moveId != mMoveId || !PackageManager.isMoveStatusFinished(status)) {
return;
}
if (status == PackageManager.MOVE_SUCCEEDED) {
showMigrationSuccessToast();
} else {
showMigrationFailureToast();
}
finish();
}
};
public static Intent getLaunchIntent(Context context, String volumeId,
boolean migrateHere) {
final Intent i = new Intent(context, MigrateStorageActivity.class);
i.putExtra(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
i.putExtra(EXTRA_MIGRATE_HERE, migrateHere);
return i;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
final String volumeId = intent.getStringExtra(VolumeInfo.EXTRA_VOLUME_ID);
final StorageManager storageManager = getSystemService(StorageManager.class);
if (intent.getBooleanExtra(EXTRA_MIGRATE_HERE, true)) {
mTargetVolumeInfo = storageManager.findVolumeById(volumeId);
if (mTargetVolumeInfo == null) {
finish();
return;
}
mTargetVolumeDesc = storageManager.getBestVolumeDescription(mTargetVolumeInfo);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content,
MigrateConfirmationStepFragment.newInstance(mTargetVolumeDesc))
.commitAllowingStateLoss();
} else {
mVolumeInfo = storageManager.findVolumeById(volumeId);
if (mVolumeInfo == null) {
finish();
return;
}
mVolumeDesc = storageManager.getBestVolumeDescription(mVolumeInfo);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content,
ChooseStorageStepFragment.newInstance(mVolumeInfo))
.commitAllowingStateLoss();
}
mPackageManager = getPackageManager();
mPackageManager.registerMoveCallback(mMoveCallback, mHandler);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(SAVE_STATE_MOVE_ID, mMoveId);
}
@Override
protected void onDestroy() {
super.onDestroy();
getPackageManager().unregisterMoveCallback(mMoveCallback);
}
private void onConfirmCancel() {
finish();
}
private void onConfirmProceed() {
startMigrationInternal();
}
private void onChoose(VolumeInfo volumeInfo) {
mTargetVolumeInfo = volumeInfo;
final StorageManager storageManager = getSystemService(StorageManager.class);
mTargetVolumeDesc = storageManager.getBestVolumeDescription(mTargetVolumeInfo);
startMigrationInternal();
}
private void startMigrationInternal() {
try {
mMoveId = mPackageManager.movePrimaryStorage(mTargetVolumeInfo);
getSupportFragmentManager().beginTransaction()
.replace(android.R.id.content,
MigrateProgressFragment.newInstance(mTargetVolumeDesc))
.commitNowAllowingStateLoss();
} catch (IllegalArgumentException e) {
// This will generally happen if there's a move already in progress or completed
StorageManager sm = (StorageManager) getSystemService(STORAGE_SERVICE);
if (Objects.equals(mTargetVolumeInfo.getFsUuid(),
sm.getPrimaryStorageVolume().getUuid())) {
// The data is already on the target volume
showMigrationSuccessToast();
} else {
// The data is most likely in the process of being moved
Log.e(TAG, "Storage migration failure", e);
showMigrationFailureToast();
}
finish();
} catch (IllegalStateException e) {
showMigrationFailureToast();
finish();
}
}
private void showMigrationSuccessToast() {
Toast.makeText(this,
getString(R.string.storage_wizard_migrate_toast_success, mTargetVolumeDesc),
Toast.LENGTH_SHORT).show();
}
private void showMigrationFailureToast() {
Toast.makeText(this,
getString(R.string.storage_wizard_migrate_toast_failure, mTargetVolumeDesc),
Toast.LENGTH_SHORT).show();
}
public static class MigrateConfirmationStepFragment extends GuidedStepSupportFragment {
private static final String ARG_VOLUME_DESC = "volumeDesc";
private static final int ACTION_CONFIRM = 1;
private static final int ACTION_LATER = 2;
public static MigrateConfirmationStepFragment newInstance(String volumeDescription) {
final MigrateConfirmationStepFragment fragment = new MigrateConfirmationStepFragment();
final Bundle b = new Bundle(1);
b.putString(ARG_VOLUME_DESC, volumeDescription);
fragment.setArguments(b);
return fragment;
}
@NonNull
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
final String driveDesc = getArguments().getString(ARG_VOLUME_DESC);
return new GuidanceStylist.Guidance(
getString(R.string.storage_wizard_migrate_confirm_title, driveDesc),
getString(R.string.storage_wizard_migrate_confirm_description, driveDesc),
null,
getActivity().getDrawable(R.drawable.ic_storage_132dp));
}
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions,
Bundle savedInstanceState) {
actions.add(new GuidedAction.Builder(getContext())
.id(ACTION_CONFIRM)
.title(R.string.storage_wizard_migrate_confirm_action_move_now)
.build());
actions.add(new GuidedAction.Builder(getContext())
.id(ACTION_LATER)
.title(R.string.storage_wizard_migrate_confirm_action_move_later)
.build());
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
final int id = (int) action.getId();
switch (id) {
case ACTION_CONFIRM:
((MigrateStorageActivity) getActivity()).onConfirmProceed();
break;
case ACTION_LATER:
((MigrateStorageActivity) getActivity()).onConfirmCancel();
break;
}
}
}
public static class ChooseStorageStepFragment extends GuidedStepSupportFragment {
private List<VolumeInfo> mCandidateVolumes;
public static ChooseStorageStepFragment newInstance(VolumeInfo currentVolumeInfo) {
Bundle args = new Bundle(1);
args.putString(VolumeInfo.EXTRA_VOLUME_ID, currentVolumeInfo.getId());
ChooseStorageStepFragment fragment = new ChooseStorageStepFragment();
fragment.setArguments(args);
return fragment;
}
@NonNull
@Override
public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
return new GuidanceStylist.Guidance(
getString(R.string.storage_wizard_migrate_choose_title),
null,
null,
getActivity().getDrawable(R.drawable.ic_storage_132dp));
}
@Override
public void onCreateActions(@NonNull List<GuidedAction> actions,
Bundle savedInstanceState) {
final StorageManager storageManager =
getContext().getSystemService(StorageManager.class);
mCandidateVolumes =
getContext().getPackageManager().getPrimaryStorageCandidateVolumes();
final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
for (final VolumeInfo candidate : mCandidateVolumes) {
if (TextUtils.equals(candidate.getId(), volumeId)) {
continue;
}
final File path = candidate.getPath();
final String avail = Formatter.formatFileSize(getActivity(), path.getFreeSpace());
actions.add(new GuidedAction.Builder(getContext())
.title(storageManager.getBestVolumeDescription(candidate))
.description(getString(
R.string.storage_wizard_back_up_apps_space_available, avail))
.id(mCandidateVolumes.indexOf(candidate))
.build());
}
}
@Override
public void onGuidedActionClicked(GuidedAction action) {
final VolumeInfo volumeInfo = mCandidateVolumes.get((int) action.getId());
((MigrateStorageActivity)getActivity()).onChoose(volumeInfo);
}
}
public static class MigrateProgressFragment extends ProgressDialogFragment {
private static final String ARG_VOLUME_DESC = "volumeDesc";
public static MigrateProgressFragment newInstance(String volumeDescription) {
final MigrateProgressFragment fragment = new MigrateProgressFragment();
final Bundle b = new Bundle(1);
b.putString(ARG_VOLUME_DESC, volumeDescription);
fragment.setArguments(b);
return fragment;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setTitle(getActivity().getString(R.string.storage_wizard_migrate_progress_title,
getArguments().getString(ARG_VOLUME_DESC)));
setSummary(getActivity()
.getString(R.string.storage_wizard_migrate_progress_description));
}
}
}