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