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