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