• 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 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