• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.settings.deviceinfo;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.IPackageMoveObserver;
26 import android.os.AsyncTask;
27 import android.os.Bundle;
28 import android.os.storage.DiskInfo;
29 import android.os.storage.StorageManager;
30 import android.os.storage.VolumeInfo;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.view.View;
34 import android.widget.Toast;
35 
36 import com.android.internal.logging.nano.MetricsProto;
37 import com.android.settings.R;
38 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
39 
40 import java.util.Objects;
41 
42 import static com.android.settings.deviceinfo.StorageSettings.TAG;
43 
44 public class StorageWizardFormatProgress extends StorageWizardBase {
45     private static final String TAG_SLOW_WARNING = "slow_warning";
46 
47     private boolean mFormatPrivate;
48 
49     private PartitionTask mTask;
50 
51     @Override
onCreate(Bundle savedInstanceState)52     protected void onCreate(Bundle savedInstanceState) {
53         super.onCreate(savedInstanceState);
54         if (mDisk == null) {
55             finish();
56             return;
57         }
58         setContentView(R.layout.storage_wizard_progress);
59         setKeepScreenOn(true);
60 
61         mFormatPrivate = getIntent().getBooleanExtra(
62                 StorageWizardFormatConfirm.EXTRA_FORMAT_PRIVATE, false);
63         setIllustrationType(
64                 mFormatPrivate ? ILLUSTRATION_INTERNAL : ILLUSTRATION_PORTABLE);
65 
66         setHeaderText(R.string.storage_wizard_format_progress_title, mDisk.getDescription());
67         setBodyText(R.string.storage_wizard_format_progress_body, mDisk.getDescription());
68 
69         getNextButton().setVisibility(View.GONE);
70 
71         mTask = (PartitionTask) getLastNonConfigurationInstance();
72         if (mTask == null) {
73             mTask = new PartitionTask();
74             mTask.setActivity(this);
75             mTask.execute();
76         } else {
77             mTask.setActivity(this);
78         }
79     }
80 
81     @Override
onRetainNonConfigurationInstance()82     public Object onRetainNonConfigurationInstance() {
83         return mTask;
84     }
85 
86     public static class PartitionTask extends AsyncTask<Void, Integer, Exception> {
87         public StorageWizardFormatProgress mActivity;
88 
89         private volatile int mProgress = 20;
90 
91         private volatile long mPrivateBench;
92 
93         @Override
doInBackground(Void... params)94         protected Exception doInBackground(Void... params) {
95             final StorageWizardFormatProgress activity = mActivity;
96             final StorageManager storage = mActivity.mStorage;
97             try {
98                 if (activity.mFormatPrivate) {
99                     storage.partitionPrivate(activity.mDisk.getId());
100                     publishProgress(40);
101 
102                     final VolumeInfo privateVol = activity.findFirstVolume(VolumeInfo.TYPE_PRIVATE);
103                     mPrivateBench = storage.benchmark(privateVol.getId());
104                     mPrivateBench /= 1000000;
105 
106                     // If we just adopted the device that had been providing
107                     // physical storage, then automatically move storage to the
108                     // new emulated volume.
109                     if (activity.mDisk.isDefaultPrimary()
110                             && Objects.equals(storage.getPrimaryStorageUuid(),
111                                     StorageManager.UUID_PRIMARY_PHYSICAL)) {
112                         Log.d(TAG, "Just formatted primary physical; silently moving "
113                                 + "storage to new emulated volume");
114                         storage.setPrimaryStorageUuid(privateVol.getFsUuid(), new SilentObserver());
115                     }
116 
117                 } else {
118                     storage.partitionPublic(activity.mDisk.getId());
119                 }
120                 return null;
121             } catch (Exception e) {
122                 return e;
123             }
124         }
125 
126         @Override
onProgressUpdate(Integer... progress)127         protected void onProgressUpdate(Integer... progress) {
128             mProgress = progress[0];
129             mActivity.setCurrentProgress(mProgress);
130         }
131 
setActivity(StorageWizardFormatProgress activity)132         public void setActivity(StorageWizardFormatProgress activity) {
133             mActivity = activity;
134             mActivity.setCurrentProgress(mProgress);
135         }
136 
137         @Override
onPostExecute(Exception e)138         protected void onPostExecute(Exception e) {
139             final StorageWizardFormatProgress activity = mActivity;
140             if (activity.isDestroyed()) {
141                 return;
142             }
143 
144             if (e != null) {
145                 Log.e(TAG, "Failed to partition", e);
146                 Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show();
147                 activity.finishAffinity();
148                 return;
149             }
150 
151             if (activity.mFormatPrivate) {
152                 // When the adoptable storage feature originally launched, we
153                 // benchmarked both internal storage and the newly adopted
154                 // storage and we warned if the adopted device was less than
155                 // 0.25x the speed of internal. (The goal was to help set user
156                 // expectations and encourage use of devices comparable to
157                 // internal storage performance.)
158 
159                 // However, since then, internal storage has started moving from
160                 // eMMC to UFS, which can significantly outperform adopted
161                 // devices, causing the speed warning to always trigger. To
162                 // mitigate this, we've switched to using a static threshold.
163 
164                 // The static threshold was derived by running the benchmark on
165                 // a wide selection of SD cards from several vendors; here are
166                 // some 50th percentile results from 20+ runs of each card:
167 
168                 // 8GB C4 40MB/s+: 3282ms
169                 // 16GB C10 40MB/s+: 1881ms
170                 // 32GB C10 40MB/s+: 2897ms
171                 // 32GB U3 80MB/s+: 1595ms
172                 // 32GB C10 80MB/s+: 1680ms
173                 // 128GB U1 80MB/s+: 1532ms
174 
175                 // Thus a 2000ms static threshold strikes a reasonable balance
176                 // to help us identify slower cards. Users can still proceed
177                 // with these slower cards; we're just showing a warning.
178 
179                 // The above analysis was done using the "r1572:w1001:s285"
180                 // benchmark, and it should be redone any time the benchmark
181                 // changes.
182 
183                 Log.d(TAG, "New volume took " + mPrivateBench + "ms to run benchmark");
184                 if (mPrivateBench > 2000) {
185                     final SlowWarningFragment dialog = new SlowWarningFragment();
186                     dialog.showAllowingStateLoss(activity.getFragmentManager(), TAG_SLOW_WARNING);
187                 } else {
188                     activity.onFormatFinished();
189                 }
190             } else {
191                 activity.onFormatFinished();
192             }
193         }
194     }
195 
196     public static class SlowWarningFragment extends InstrumentedDialogFragment {
197 
198         @Override
getMetricsCategory()199         public int getMetricsCategory() {
200             return MetricsProto.MetricsEvent.DIALOG_VOLUME_SLOW_WARNING;
201         }
202 
203         @Override
onCreateDialog(Bundle savedInstanceState)204         public Dialog onCreateDialog(Bundle savedInstanceState) {
205             final Context context = getActivity();
206 
207             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
208 
209             final StorageWizardFormatProgress target =
210                     (StorageWizardFormatProgress) getActivity();
211             final String descrip = target.getDiskDescription();
212             final String genericDescip = target.getGenericDiskDescription();
213             builder.setMessage(TextUtils.expandTemplate(getText(R.string.storage_wizard_slow_body),
214                     descrip, genericDescip));
215 
216             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
217                 @Override
218                 public void onClick(DialogInterface dialog, int which) {
219                     final StorageWizardFormatProgress target =
220                             (StorageWizardFormatProgress) getActivity();
221                     target.onFormatFinished();
222                 }
223             });
224 
225             return builder.create();
226         }
227     }
228 
getDiskDescription()229     private String getDiskDescription() {
230         return mDisk.getDescription();
231     }
232 
getGenericDiskDescription()233     private String getGenericDiskDescription() {
234         // TODO: move this directly to DiskInfo
235         if (mDisk.isSd()) {
236             return getString(com.android.internal.R.string.storage_sd_card);
237         } else if (mDisk.isUsb()) {
238             return getString(com.android.internal.R.string.storage_usb_drive);
239         } else {
240             return null;
241         }
242     }
243 
onFormatFinished()244     private void onFormatFinished() {
245         final String forgetUuid = getIntent().getStringExtra(
246                 StorageWizardFormatConfirm.EXTRA_FORGET_UUID);
247         if (!TextUtils.isEmpty(forgetUuid)) {
248             mStorage.forgetVolume(forgetUuid);
249         }
250 
251         final boolean offerMigrate;
252         if (mFormatPrivate) {
253             // Offer to migrate only if storage is currently internal
254             final VolumeInfo privateVol = getPackageManager()
255                     .getPrimaryStorageCurrentVolume();
256             offerMigrate = (privateVol != null
257                     && VolumeInfo.ID_PRIVATE_INTERNAL.equals(privateVol.getId()));
258         } else {
259             offerMigrate = false;
260         }
261 
262         if (offerMigrate) {
263             final Intent intent = new Intent(this, StorageWizardMigrate.class);
264             intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
265             startActivity(intent);
266         } else {
267             final Intent intent = new Intent(this, StorageWizardReady.class);
268             intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
269             startActivity(intent);
270         }
271         finishAffinity();
272     }
273 
274     private static class SilentObserver extends IPackageMoveObserver.Stub {
275         @Override
onCreated(int moveId, Bundle extras)276         public void onCreated(int moveId, Bundle extras) {
277             // Ignored
278         }
279 
280         @Override
onStatusChanged(int moveId, int status, long estMillis)281         public void onStatusChanged(int moveId, int status, long estMillis) {
282             // Ignored
283         }
284     }
285 }
286