1 /* 2 * Copyright (C) 2015 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.tv.settings.device.storage; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.storage.StorageManager; 26 import android.os.storage.VolumeInfo; 27 import android.text.TextUtils; 28 import android.text.format.Formatter; 29 import android.util.Log; 30 import android.view.View; 31 import android.widget.Toast; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.leanback.app.GuidedStepFragment; 36 import androidx.leanback.widget.GuidanceStylist; 37 import androidx.leanback.widget.GuidedAction; 38 39 import com.android.tv.settings.R; 40 import com.android.tv.settings.dialog.ProgressDialogFragment; 41 42 import java.io.File; 43 import java.util.List; 44 import java.util.Objects; 45 46 public class MigrateStorageActivity extends Activity { 47 private static final String TAG = "MigrateStorageActivity"; 48 49 private static final String EXTRA_MIGRATE_HERE = 50 "com.android.tv.settings.device.storage.MigrateStorageActivity.MIGRATE_HERE"; 51 52 private static final String SAVE_STATE_MOVE_ID = "MigrateStorageActivity.MOVE_ID"; 53 54 private VolumeInfo mTargetVolumeInfo; 55 private VolumeInfo mVolumeInfo; 56 private String mTargetVolumeDesc; 57 private String mVolumeDesc; 58 private int mMoveId = -1; 59 private final Handler mHandler = new Handler(); 60 private PackageManager mPackageManager; 61 private final PackageManager.MoveCallback mMoveCallback = new PackageManager.MoveCallback() { 62 @Override 63 public void onStatusChanged(int moveId, int status, long estMillis) { 64 if (moveId != mMoveId || !PackageManager.isMoveStatusFinished(status)) { 65 return; 66 } 67 if (status == PackageManager.MOVE_SUCCEEDED) { 68 showMigrationSuccessToast(); 69 } else { 70 showMigrationFailureToast(); 71 } 72 finish(); 73 } 74 }; 75 getLaunchIntent(Context context, String volumeId, boolean migrateHere)76 public static Intent getLaunchIntent(Context context, String volumeId, 77 boolean migrateHere) { 78 final Intent i = new Intent(context, MigrateStorageActivity.class); 79 i.putExtra(VolumeInfo.EXTRA_VOLUME_ID, volumeId); 80 i.putExtra(EXTRA_MIGRATE_HERE, migrateHere); 81 return i; 82 } 83 84 @Override onCreate(@ullable Bundle savedInstanceState)85 protected void onCreate(@Nullable Bundle savedInstanceState) { 86 super.onCreate(savedInstanceState); 87 88 final Intent intent = getIntent(); 89 final String volumeId = intent.getStringExtra(VolumeInfo.EXTRA_VOLUME_ID); 90 final StorageManager storageManager = getSystemService(StorageManager.class); 91 92 if (intent.getBooleanExtra(EXTRA_MIGRATE_HERE, true)) { 93 mTargetVolumeInfo = storageManager.findVolumeById(volumeId); 94 if (mTargetVolumeInfo == null) { 95 finish(); 96 return; 97 } 98 mTargetVolumeDesc = storageManager.getBestVolumeDescription(mTargetVolumeInfo); 99 getFragmentManager().beginTransaction() 100 .add(android.R.id.content, 101 MigrateConfirmationStepFragment.newInstance(mTargetVolumeDesc)) 102 .commit(); 103 } else { 104 mVolumeInfo = storageManager.findVolumeById(volumeId); 105 if (mVolumeInfo == null) { 106 finish(); 107 return; 108 } 109 mVolumeDesc = storageManager.getBestVolumeDescription(mVolumeInfo); 110 getFragmentManager().beginTransaction() 111 .add(android.R.id.content, 112 ChooseStorageStepFragment.newInstance(mVolumeInfo)) 113 .commit(); 114 } 115 116 mPackageManager = getPackageManager(); 117 mPackageManager.registerMoveCallback(mMoveCallback, mHandler); 118 } 119 120 @Override onSaveInstanceState(@onNull Bundle outState)121 protected void onSaveInstanceState(@NonNull Bundle outState) { 122 super.onSaveInstanceState(outState); 123 outState.putInt(SAVE_STATE_MOVE_ID, mMoveId); 124 } 125 126 @Override onDestroy()127 protected void onDestroy() { 128 super.onDestroy(); 129 getPackageManager().unregisterMoveCallback(mMoveCallback); 130 } 131 onConfirmCancel()132 private void onConfirmCancel() { 133 finish(); 134 } 135 onConfirmProceed()136 private void onConfirmProceed() { 137 startMigrationInternal(); 138 } 139 onChoose(VolumeInfo volumeInfo)140 private void onChoose(VolumeInfo volumeInfo) { 141 mTargetVolumeInfo = volumeInfo; 142 final StorageManager storageManager = getSystemService(StorageManager.class); 143 mTargetVolumeDesc = storageManager.getBestVolumeDescription(mTargetVolumeInfo); 144 startMigrationInternal(); 145 } 146 startMigrationInternal()147 private void startMigrationInternal() { 148 try { 149 mMoveId = mPackageManager.movePrimaryStorage(mTargetVolumeInfo); 150 getFragmentManager().beginTransaction() 151 .replace(android.R.id.content, 152 MigrateProgressFragment.newInstance(mTargetVolumeDesc)) 153 .commitNow(); 154 } catch (IllegalArgumentException e) { 155 // This will generally happen if there's a move already in progress or completed 156 StorageManager sm = (StorageManager) getSystemService(STORAGE_SERVICE); 157 158 if (Objects.equals(mTargetVolumeInfo.getFsUuid(), 159 sm.getPrimaryStorageVolume().getUuid())) { 160 // The data is already on the target volume 161 showMigrationSuccessToast(); 162 } else { 163 // The data is most likely in the process of being moved 164 Log.e(TAG, "Storage migration failure", e); 165 showMigrationFailureToast(); 166 } 167 finish(); 168 } catch (IllegalStateException e) { 169 showMigrationFailureToast(); 170 finish(); 171 } 172 } 173 showMigrationSuccessToast()174 private void showMigrationSuccessToast() { 175 Toast.makeText(this, 176 getString(R.string.storage_wizard_migrate_toast_success, mTargetVolumeDesc), 177 Toast.LENGTH_SHORT).show(); 178 } 179 showMigrationFailureToast()180 private void showMigrationFailureToast() { 181 Toast.makeText(this, 182 getString(R.string.storage_wizard_migrate_toast_failure, mTargetVolumeDesc), 183 Toast.LENGTH_SHORT).show(); 184 } 185 186 public static class MigrateConfirmationStepFragment extends GuidedStepFragment { 187 private static final String ARG_VOLUME_DESC = "volumeDesc"; 188 189 private static final int ACTION_CONFIRM = 1; 190 private static final int ACTION_LATER = 2; 191 newInstance(String volumeDescription)192 public static MigrateConfirmationStepFragment newInstance(String volumeDescription) { 193 final MigrateConfirmationStepFragment fragment = new MigrateConfirmationStepFragment(); 194 final Bundle b = new Bundle(1); 195 b.putString(ARG_VOLUME_DESC, volumeDescription); 196 fragment.setArguments(b); 197 return fragment; 198 } 199 200 @NonNull 201 @Override onCreateGuidance(Bundle savedInstanceState)202 public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { 203 final String driveDesc = getArguments().getString(ARG_VOLUME_DESC); 204 return new GuidanceStylist.Guidance( 205 getString(R.string.storage_wizard_migrate_confirm_title, driveDesc), 206 getString(R.string.storage_wizard_migrate_confirm_description, driveDesc), 207 null, 208 getActivity().getDrawable(R.drawable.ic_storage_132dp)); 209 } 210 211 @Override onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)212 public void onCreateActions(@NonNull List<GuidedAction> actions, 213 Bundle savedInstanceState) { 214 actions.add(new GuidedAction.Builder(getContext()) 215 .id(ACTION_CONFIRM) 216 .title(R.string.storage_wizard_migrate_confirm_action_move_now) 217 .build()); 218 actions.add(new GuidedAction.Builder(getContext()) 219 .id(ACTION_LATER) 220 .title(R.string.storage_wizard_migrate_confirm_action_move_later) 221 .build()); 222 } 223 224 @Override onGuidedActionClicked(GuidedAction action)225 public void onGuidedActionClicked(GuidedAction action) { 226 final int id = (int) action.getId(); 227 switch (id) { 228 case ACTION_CONFIRM: 229 ((MigrateStorageActivity) getActivity()).onConfirmProceed(); 230 break; 231 case ACTION_LATER: 232 ((MigrateStorageActivity) getActivity()).onConfirmCancel(); 233 break; 234 } 235 } 236 } 237 238 public static class ChooseStorageStepFragment extends GuidedStepFragment { 239 240 private List<VolumeInfo> mCandidateVolumes; 241 newInstance(VolumeInfo currentVolumeInfo)242 public static ChooseStorageStepFragment newInstance(VolumeInfo currentVolumeInfo) { 243 Bundle args = new Bundle(1); 244 args.putString(VolumeInfo.EXTRA_VOLUME_ID, currentVolumeInfo.getId()); 245 246 ChooseStorageStepFragment fragment = new ChooseStorageStepFragment(); 247 fragment.setArguments(args); 248 return fragment; 249 } 250 251 @NonNull 252 @Override onCreateGuidance(Bundle savedInstanceState)253 public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { 254 return new GuidanceStylist.Guidance( 255 getString(R.string.storage_wizard_migrate_choose_title), 256 null, 257 null, 258 getActivity().getDrawable(R.drawable.ic_storage_132dp)); 259 } 260 261 @Override onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)262 public void onCreateActions(@NonNull List<GuidedAction> actions, 263 Bundle savedInstanceState) { 264 final StorageManager storageManager = 265 getContext().getSystemService(StorageManager.class); 266 mCandidateVolumes = 267 getContext().getPackageManager().getPrimaryStorageCandidateVolumes(); 268 final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID); 269 for (final VolumeInfo candidate : mCandidateVolumes) { 270 if (TextUtils.equals(candidate.getId(), volumeId)) { 271 continue; 272 } 273 final File path = candidate.getPath(); 274 final String avail = Formatter.formatFileSize(getActivity(), path.getFreeSpace()); 275 actions.add(new GuidedAction.Builder(getContext()) 276 .title(storageManager.getBestVolumeDescription(candidate)) 277 .description(getString( 278 R.string.storage_wizard_back_up_apps_space_available, avail)) 279 .id(mCandidateVolumes.indexOf(candidate)) 280 .build()); 281 } 282 283 } 284 285 @Override onGuidedActionClicked(GuidedAction action)286 public void onGuidedActionClicked(GuidedAction action) { 287 final VolumeInfo volumeInfo = mCandidateVolumes.get((int) action.getId()); 288 ((MigrateStorageActivity)getActivity()).onChoose(volumeInfo); 289 } 290 } 291 292 public static class MigrateProgressFragment extends ProgressDialogFragment { 293 private static final String ARG_VOLUME_DESC = "volumeDesc"; 294 newInstance(String volumeDescription)295 public static MigrateProgressFragment newInstance(String volumeDescription) { 296 final MigrateProgressFragment fragment = new MigrateProgressFragment(); 297 final Bundle b = new Bundle(1); 298 b.putString(ARG_VOLUME_DESC, volumeDescription); 299 fragment.setArguments(b); 300 return fragment; 301 } 302 303 @Override onViewCreated(View view, @Nullable Bundle savedInstanceState)304 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 305 super.onViewCreated(view, savedInstanceState); 306 setTitle(getActivity().getString(R.string.storage_wizard_migrate_progress_title, 307 getArguments().getString(ARG_VOLUME_DESC))); 308 setSummary(getActivity() 309 .getString(R.string.storage_wizard_migrate_progress_description)); 310 } 311 312 } 313 } 314