Home | History | Annotate | Download | only in deviceinfo
      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 android.app.AlertDialog;
     20 import android.app.Dialog;
     21 import android.app.DialogFragment;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.Intent;
     25 import android.content.pm.IPackageMoveObserver;
     26 import android.os.AsyncTask;
     27 import android.os.Bundle;
     28 import android.os.storage.DiskInfo;
     29 import android.os.storage.StorageManager;
     30 import android.os.storage.VolumeInfo;
     31 import android.text.TextUtils;
     32 import android.util.Log;
     33 import android.view.View;
     34 import android.widget.Toast;
     35 
     36 import com.android.internal.logging.nano.MetricsProto;
     37 import com.android.settings.R;
     38 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
     39 
     40 import java.util.Objects;
     41 
     42 import static com.android.settings.deviceinfo.StorageSettings.TAG;
     43 
     44 public class StorageWizardFormatProgress extends StorageWizardBase {
     45     private static final String TAG_SLOW_WARNING = "slow_warning";
     46 
     47     private boolean mFormatPrivate;
     48 
     49     private PartitionTask mTask;
     50 
     51     @Override
     52     protected void onCreate(Bundle savedInstanceState) {
     53         super.onCreate(savedInstanceState);
     54         if (mDisk == null) {
     55             finish();
     56             return;
     57         }
     58         setContentView(R.layout.storage_wizard_progress);
     59         setKeepScreenOn(true);
     60 
     61         mFormatPrivate = getIntent().getBooleanExtra(
     62                 StorageWizardFormatConfirm.EXTRA_FORMAT_PRIVATE, false);
     63         setIllustrationType(
     64                 mFormatPrivate ? ILLUSTRATION_INTERNAL : ILLUSTRATION_PORTABLE);
     65 
     66         setHeaderText(R.string.storage_wizard_format_progress_title, mDisk.getDescription());
     67         setBodyText(R.string.storage_wizard_format_progress_body, mDisk.getDescription());
     68 
     69         getNextButton().setVisibility(View.GONE);
     70 
     71         mTask = (PartitionTask) getLastNonConfigurationInstance();
     72         if (mTask == null) {
     73             mTask = new PartitionTask();
     74             mTask.setActivity(this);
     75             mTask.execute();
     76         } else {
     77             mTask.setActivity(this);
     78         }
     79     }
     80 
     81     @Override
     82     public Object onRetainNonConfigurationInstance() {
     83         return mTask;
     84     }
     85 
     86     public static class PartitionTask extends AsyncTask<Void, Integer, Exception> {
     87         public StorageWizardFormatProgress mActivity;
     88 
     89         private volatile int mProgress = 20;
     90 
     91         private volatile long mPrivateBench;
     92 
     93         @Override
     94         protected Exception doInBackground(Void... params) {
     95             final StorageWizardFormatProgress activity = mActivity;
     96             final StorageManager storage = mActivity.mStorage;
     97             try {
     98                 if (activity.mFormatPrivate) {
     99                     storage.partitionPrivate(activity.mDisk.getId());
    100                     publishProgress(40);
    101 
    102                     final VolumeInfo privateVol = activity.findFirstVolume(VolumeInfo.TYPE_PRIVATE);
    103                     mPrivateBench = storage.benchmark(privateVol.getId());
    104                     mPrivateBench /= 1000000;
    105 
    106                     // If we just adopted the device that had been providing
    107                     // physical storage, then automatically move storage to the
    108                     // new emulated volume.
    109                     if (activity.mDisk.isDefaultPrimary()
    110                             && Objects.equals(storage.getPrimaryStorageUuid(),
    111                                     StorageManager.UUID_PRIMARY_PHYSICAL)) {
    112                         Log.d(TAG, "Just formatted primary physical; silently moving "
    113                                 + "storage to new emulated volume");
    114                         storage.setPrimaryStorageUuid(privateVol.getFsUuid(), new SilentObserver());
    115                     }
    116 
    117                 } else {
    118                     storage.partitionPublic(activity.mDisk.getId());
    119                 }
    120                 return null;
    121             } catch (Exception e) {
    122                 return e;
    123             }
    124         }
    125 
    126         @Override
    127         protected void onProgressUpdate(Integer... progress) {
    128             mProgress = progress[0];
    129             mActivity.setCurrentProgress(mProgress);
    130         }
    131 
    132         public void setActivity(StorageWizardFormatProgress activity) {
    133             mActivity = activity;
    134             mActivity.setCurrentProgress(mProgress);
    135         }
    136 
    137         @Override
    138         protected void onPostExecute(Exception e) {
    139             final StorageWizardFormatProgress activity = mActivity;
    140             if (activity.isDestroyed()) {
    141                 return;
    142             }
    143 
    144             if (e != null) {
    145                 Log.e(TAG, "Failed to partition", e);
    146                 Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show();
    147                 activity.finishAffinity();
    148                 return;
    149             }
    150 
    151             if (activity.mFormatPrivate) {
    152                 // When the adoptable storage feature originally launched, we
    153                 // benchmarked both internal storage and the newly adopted
    154                 // storage and we warned if the adopted device was less than
    155                 // 0.25x the speed of internal. (The goal was to help set user
    156                 // expectations and encourage use of devices comparable to
    157                 // internal storage performance.)
    158 
    159                 // However, since then, internal storage has started moving from
    160                 // eMMC to UFS, which can significantly outperform adopted
    161                 // devices, causing the speed warning to always trigger. To
    162                 // mitigate this, we've switched to using a static threshold.
    163 
    164                 // The static threshold was derived by running the benchmark on
    165                 // a wide selection of SD cards from several vendors; here are
    166                 // some 50th percentile results from 20+ runs of each card:
    167 
    168                 // 8GB C4 40MB/s+: 3282ms
    169                 // 16GB C10 40MB/s+: 1881ms
    170                 // 32GB C10 40MB/s+: 2897ms
    171                 // 32GB U3 80MB/s+: 1595ms
    172                 // 32GB C10 80MB/s+: 1680ms
    173                 // 128GB U1 80MB/s+: 1532ms
    174 
    175                 // Thus a 2000ms static threshold strikes a reasonable balance
    176                 // to help us identify slower cards. Users can still proceed
    177                 // with these slower cards; we're just showing a warning.
    178 
    179                 // The above analysis was done using the "r1572:w1001:s285"
    180                 // benchmark, and it should be redone any time the benchmark
    181                 // changes.
    182 
    183                 Log.d(TAG, "New volume took " + mPrivateBench + "ms to run benchmark");
    184                 if (mPrivateBench > 2000) {
    185                     final SlowWarningFragment dialog = new SlowWarningFragment();
    186                     dialog.showAllowingStateLoss(activity.getFragmentManager(), TAG_SLOW_WARNING);
    187                 } else {
    188                     activity.onFormatFinished();
    189                 }
    190             } else {
    191                 activity.onFormatFinished();
    192             }
    193         }
    194     }
    195 
    196     public static class SlowWarningFragment extends InstrumentedDialogFragment {
    197 
    198         @Override
    199         public int getMetricsCategory() {
    200             return MetricsProto.MetricsEvent.DIALOG_VOLUME_SLOW_WARNING;
    201         }
    202 
    203         @Override
    204         public Dialog onCreateDialog(Bundle savedInstanceState) {
    205             final Context context = getActivity();
    206 
    207             final AlertDialog.Builder builder = new AlertDialog.Builder(context);
    208 
    209             final StorageWizardFormatProgress target =
    210                     (StorageWizardFormatProgress) getActivity();
    211             final String descrip = target.getDiskDescription();
    212             final String genericDescip = target.getGenericDiskDescription();
    213             builder.setMessage(TextUtils.expandTemplate(getText(R.string.storage_wizard_slow_body),
    214                     descrip, genericDescip));
    215 
    216             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
    217                 @Override
    218                 public void onClick(DialogInterface dialog, int which) {
    219                     final StorageWizardFormatProgress target =
    220                             (StorageWizardFormatProgress) getActivity();
    221                     target.onFormatFinished();
    222                 }
    223             });
    224 
    225             return builder.create();
    226         }
    227     }
    228 
    229     private String getDiskDescription() {
    230         return mDisk.getDescription();
    231     }
    232 
    233     private String getGenericDiskDescription() {
    234         // TODO: move this directly to DiskInfo
    235         if (mDisk.isSd()) {
    236             return getString(com.android.internal.R.string.storage_sd_card);
    237         } else if (mDisk.isUsb()) {
    238             return getString(com.android.internal.R.string.storage_usb_drive);
    239         } else {
    240             return null;
    241         }
    242     }
    243 
    244     private void onFormatFinished() {
    245         final String forgetUuid = getIntent().getStringExtra(
    246                 StorageWizardFormatConfirm.EXTRA_FORGET_UUID);
    247         if (!TextUtils.isEmpty(forgetUuid)) {
    248             mStorage.forgetVolume(forgetUuid);
    249         }
    250 
    251         final boolean offerMigrate;
    252         if (mFormatPrivate) {
    253             // Offer to migrate only if storage is currently internal
    254             final VolumeInfo privateVol = getPackageManager()
    255                     .getPrimaryStorageCurrentVolume();
    256             offerMigrate = (privateVol != null
    257                     && VolumeInfo.ID_PRIVATE_INTERNAL.equals(privateVol.getId()));
    258         } else {
    259             offerMigrate = false;
    260         }
    261 
    262         if (offerMigrate) {
    263             final Intent intent = new Intent(this, StorageWizardMigrate.class);
    264             intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
    265             startActivity(intent);
    266         } else {
    267             final Intent intent = new Intent(this, StorageWizardReady.class);
    268             intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
    269             startActivity(intent);
    270         }
    271         finishAffinity();
    272     }
    273 
    274     private static class SilentObserver extends IPackageMoveObserver.Stub {
    275         @Override
    276         public void onCreated(int moveId, Bundle extras) {
    277             // Ignored
    278         }
    279 
    280         @Override
    281         public void onStatusChanged(int moveId, int status, long estMillis) {
    282             // Ignored
    283         }
    284     }
    285 }
    286