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