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