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