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.view.View;
34 import android.widget.Toast;
35 
36 import com.android.settings.R;
37 
38 import java.util.Objects;
39 import java.util.concurrent.CompletableFuture;
40 import java.util.concurrent.TimeUnit;
41 
42 public class StorageWizardFormatProgress extends StorageWizardBase {
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