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