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 static android.os.storage.VolumeInfo.TYPE_PRIVATE; 20 21 import static com.android.settings.deviceinfo.StorageSettings.TAG; 22 23 import android.content.Intent; 24 import android.content.pm.IPackageMoveObserver; 25 import android.os.AsyncTask; 26 import android.os.Bundle; 27 import android.os.IVoldTaskListener; 28 import android.os.PersistableBundle; 29 import android.os.SystemProperties; 30 import android.os.storage.StorageManager; 31 import android.os.storage.VolumeInfo; 32 import android.util.Log; 33 import android.widget.Toast; 34 35 import com.android.settings.R; 36 37 import java.util.Objects; 38 import java.util.concurrent.CompletableFuture; 39 import java.util.concurrent.TimeUnit; 40 41 public class StorageWizardFormatProgress extends StorageWizardBase { 42 private static final String PROP_DEBUG_STORAGE_SLOW = "sys.debug.storage_slow"; 43 44 private boolean mFormatPrivate; 45 46 private PartitionTask mTask; 47 48 @Override onCreate(Bundle savedInstanceState)49 protected void onCreate(Bundle savedInstanceState) { 50 super.onCreate(savedInstanceState); 51 if (mDisk == null) { 52 finish(); 53 return; 54 } 55 setContentView(R.layout.storage_wizard_progress); 56 setKeepScreenOn(true); 57 58 mFormatPrivate = getIntent().getBooleanExtra(EXTRA_FORMAT_PRIVATE, false); 59 60 setHeaderText(R.string.storage_wizard_format_progress_title, getDiskShortDescription()); 61 setBodyText(R.string.storage_wizard_format_progress_body, getDiskDescription()); 62 63 mTask = (PartitionTask) getLastNonConfigurationInstance(); 64 if (mTask == null) { 65 mTask = new PartitionTask(); 66 mTask.setActivity(this); 67 mTask.execute(); 68 } else { 69 mTask.setActivity(this); 70 } 71 } 72 73 @Override onRetainNonConfigurationInstance()74 public Object onRetainNonConfigurationInstance() { 75 return mTask; 76 } 77 78 public static class PartitionTask extends AsyncTask<Void, Integer, Exception> { 79 public StorageWizardFormatProgress mActivity; 80 81 private volatile int mProgress = 20; 82 83 private volatile long mPrivateBench; 84 85 @Override doInBackground(Void... params)86 protected Exception doInBackground(Void... params) { 87 final StorageWizardFormatProgress activity = mActivity; 88 final StorageManager storage = mActivity.mStorage; 89 try { 90 if (activity.mFormatPrivate) { 91 storage.partitionPrivate(activity.mDisk.getId()); 92 publishProgress(40); 93 94 final VolumeInfo privateVol = activity.findFirstVolume(TYPE_PRIVATE, 25); 95 final CompletableFuture<PersistableBundle> result = new CompletableFuture<>(); 96 storage.benchmark(privateVol.getId(), new IVoldTaskListener.Stub() { 97 @Override 98 public void onStatus(int status, PersistableBundle extras) { 99 // Map benchmark 0-100% progress onto 40-80% 100 publishProgress(40 + ((status * 40) / 100)); 101 } 102 103 @Override 104 public void onFinished(int status, PersistableBundle extras) { 105 result.complete(extras); 106 } 107 }); 108 mPrivateBench = result.get(60, TimeUnit.SECONDS).getLong("run", Long.MAX_VALUE); 109 110 // If we just adopted the device that had been providing 111 // physical storage, then automatically move storage to the 112 // new emulated volume. 113 if (activity.mDisk.isDefaultPrimary() 114 && Objects.equals(storage.getPrimaryStorageUuid(), 115 StorageManager.UUID_PRIMARY_PHYSICAL)) { 116 Log.d(TAG, "Just formatted primary physical; silently moving " 117 + "storage to new emulated volume"); 118 storage.setPrimaryStorageUuid(privateVol.getFsUuid(), new SilentObserver()); 119 } 120 121 } else { 122 storage.partitionPublic(activity.mDisk.getId()); 123 } 124 return null; 125 } catch (Exception e) { 126 return e; 127 } 128 } 129 130 @Override onProgressUpdate(Integer... progress)131 protected void onProgressUpdate(Integer... progress) { 132 mProgress = progress[0]; 133 mActivity.setCurrentProgress(mProgress); 134 } 135 setActivity(StorageWizardFormatProgress activity)136 public void setActivity(StorageWizardFormatProgress activity) { 137 mActivity = activity; 138 mActivity.setCurrentProgress(mProgress); 139 } 140 141 @Override onPostExecute(Exception e)142 protected void onPostExecute(Exception e) { 143 final StorageWizardFormatProgress activity = mActivity; 144 if (activity.isDestroyed()) { 145 return; 146 } 147 148 if (e != null) { 149 Log.e(TAG, "Failed to partition", e); 150 Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show(); 151 activity.finishAffinity(); 152 return; 153 } 154 155 if (activity.mFormatPrivate) { 156 // When the adoptable storage feature originally launched, we 157 // benchmarked both internal storage and the newly adopted 158 // storage and we warned if the adopted device was less than 159 // 0.25x the speed of internal. (The goal was to help set user 160 // expectations and encourage use of devices comparable to 161 // internal storage performance.) 162 163 // However, since then, internal storage has started moving from 164 // eMMC to UFS, which can significantly outperform adopted 165 // devices, causing the speed warning to always trigger. To 166 // mitigate this, we've switched to using a static threshold. 167 168 // The static threshold was derived by running the benchmark on 169 // a wide selection of SD cards from several vendors; here are 170 // some 50th percentile results from 20+ runs of each card: 171 172 // 8GB C4 40MB/s+: 3282ms 173 // 16GB C10 40MB/s+: 1881ms 174 // 32GB C10 40MB/s+: 2897ms 175 // 32GB U3 80MB/s+: 1595ms 176 // 32GB C10 80MB/s+: 1680ms 177 // 128GB U1 80MB/s+: 1532ms 178 179 // Thus a 2000ms static threshold strikes a reasonable balance 180 // to help us identify slower cards. Users can still proceed 181 // with these slower cards; we're just showing a warning. 182 183 // The above analysis was done using the "r1572:w1001:s285" 184 // benchmark, and it should be redone any time the benchmark 185 // changes. 186 187 Log.d(TAG, "New volume took " + mPrivateBench + "ms to run benchmark"); 188 if (mPrivateBench > 2000 189 || SystemProperties.getBoolean(PROP_DEBUG_STORAGE_SLOW, false)) { 190 mActivity.onFormatFinishedSlow(); 191 } else { 192 mActivity.onFormatFinished(); 193 } 194 } else { 195 mActivity.onFormatFinished(); 196 } 197 } 198 } 199 onFormatFinished()200 public void onFormatFinished() { 201 final Intent intent = new Intent(this, StorageWizardFormatSlow.class); 202 intent.putExtra(EXTRA_FORMAT_SLOW, false); 203 startActivity(intent); 204 finishAffinity(); 205 } 206 onFormatFinishedSlow()207 public void onFormatFinishedSlow() { 208 final Intent intent = new Intent(this, StorageWizardFormatSlow.class); 209 intent.putExtra(EXTRA_FORMAT_SLOW, true); 210 startActivity(intent); 211 finishAffinity(); 212 } 213 214 private static class SilentObserver extends IPackageMoveObserver.Stub { 215 @Override onCreated(int moveId, Bundle extras)216 public void onCreated(int moveId, Bundle extras) { 217 // Ignored 218 } 219 220 @Override onStatusChanged(int moveId, int status, long estMillis)221 public void onStatusChanged(int moveId, int status, long estMillis) { 222 // Ignored 223 } 224 } 225 } 226