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.tv.settings.device.storage; 18 19 import android.app.Activity; 20 import android.app.Fragment; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.PackageManager; 26 import android.os.Bundle; 27 import android.os.storage.DiskInfo; 28 import android.os.storage.StorageManager; 29 import android.os.storage.VolumeInfo; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.widget.Toast; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.annotation.VisibleForTesting; 37 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 38 39 import com.android.tv.settings.R; 40 41 import java.util.List; 42 43 public class FormatActivity extends Activity 44 implements FormatAsPrivateStepFragment.Callback, 45 FormatAsPublicStepFragment.Callback, SlowDriveStepFragment.Callback { 46 47 private static final String TAG = "FormatActivity"; 48 49 public static final String INTENT_ACTION_FORMAT_AS_PRIVATE = 50 "com.android.tv.settings.device.storage.FormatActivity.formatAsPrivate"; 51 public static final String INTENT_ACTION_FORMAT_AS_PUBLIC = 52 "com.android.tv.settings.device.storage.FormatActivity.formatAsPublic"; 53 54 private static final String SAVE_STATE_FORMAT_PRIVATE_DISK_ID = 55 "StorageResetActivity.formatPrivateDiskId"; 56 private static final String SAVE_STATE_FORMAT_DISK_DESC = 57 "StorageResetActivity.formatDiskDesc"; 58 private static final String SAVE_STATE_FORMAT_PUBLIC_DISK_ID = 59 "StorageResetActivity.formatPrivateDiskId"; 60 61 // Non-null means we're in the process of formatting this volume as private 62 @VisibleForTesting 63 String mFormatAsPrivateDiskId; 64 // Non-null means we're in the process of formatting this volume as public 65 @VisibleForTesting 66 String mFormatAsPublicDiskId; 67 68 private String mFormatDiskDesc; 69 70 private final BroadcastReceiver mFormatReceiver = new FormatReceiver(this); 71 private PackageManager mPackageManager; 72 private StorageManager mStorageManager; 73 getFormatAsPublicIntent(Context context, String diskId)74 public static Intent getFormatAsPublicIntent(Context context, String diskId) { 75 final Intent i = new Intent(context, FormatActivity.class); 76 i.setAction(INTENT_ACTION_FORMAT_AS_PUBLIC); 77 i.putExtra(DiskInfo.EXTRA_DISK_ID, diskId); 78 return i; 79 } 80 getFormatAsPrivateIntent(Context context, String diskId)81 public static Intent getFormatAsPrivateIntent(Context context, String diskId) { 82 final Intent i = new Intent(context, FormatActivity.class); 83 i.setAction(INTENT_ACTION_FORMAT_AS_PRIVATE); 84 i.putExtra(DiskInfo.EXTRA_DISK_ID, diskId); 85 return i; 86 } 87 88 @Override onCreate(@ullable Bundle savedInstanceState)89 protected void onCreate(@Nullable Bundle savedInstanceState) { 90 super.onCreate(savedInstanceState); 91 92 mPackageManager = getPackageManager(); 93 mStorageManager = getSystemService(StorageManager.class); 94 95 final IntentFilter filter = new IntentFilter(); 96 filter.addAction(SettingsStorageService.ACTION_FORMAT_AS_PRIVATE); 97 filter.addAction(SettingsStorageService.ACTION_FORMAT_AS_PUBLIC); 98 LocalBroadcastManager.getInstance(this).registerReceiver(mFormatReceiver, filter); 99 100 if (savedInstanceState != null) { 101 mFormatAsPrivateDiskId = 102 savedInstanceState.getString(SAVE_STATE_FORMAT_PRIVATE_DISK_ID); 103 mFormatAsPublicDiskId = savedInstanceState.getString(SAVE_STATE_FORMAT_PUBLIC_DISK_ID); 104 mFormatDiskDesc = savedInstanceState.getString(SAVE_STATE_FORMAT_DISK_DESC); 105 } else { 106 final String diskId = getIntent().getStringExtra(DiskInfo.EXTRA_DISK_ID); 107 final String action = getIntent().getAction(); 108 final Fragment f; 109 if (TextUtils.equals(action, INTENT_ACTION_FORMAT_AS_PRIVATE)) { 110 f = FormatAsPrivateStepFragment.newInstance(diskId); 111 } else if (TextUtils.equals(action, INTENT_ACTION_FORMAT_AS_PUBLIC)) { 112 f = FormatAsPublicStepFragment.newInstance(diskId); 113 } else { 114 throw new IllegalStateException("No known action specified"); 115 } 116 getFragmentManager().beginTransaction() 117 .add(android.R.id.content, f) 118 .commit(); 119 } 120 } 121 122 @Override onResume()123 protected void onResume() { 124 super.onResume(); 125 if (!TextUtils.isEmpty(mFormatAsPrivateDiskId)) { 126 final VolumeInfo volumeInfo = findVolume(mFormatAsPrivateDiskId); 127 if (volumeInfo != null && volumeInfo.getType() == VolumeInfo.TYPE_PRIVATE) { 128 // Formatting must have completed while we were paused 129 // We've lost the benchmark data, so just assume the drive is fast enough 130 handleFormatAsPrivateComplete(-1, -1); 131 } 132 } 133 } 134 135 @Override onDestroy()136 protected void onDestroy() { 137 super.onDestroy(); 138 LocalBroadcastManager.getInstance(this).unregisterReceiver(mFormatReceiver); 139 } 140 findVolume(String diskId)141 private VolumeInfo findVolume(String diskId) { 142 final List<VolumeInfo> vols = mStorageManager.getVolumes(); 143 for (final VolumeInfo vol : vols) { 144 if (TextUtils.equals(diskId, vol.getDiskId()) 145 && (vol.getType() == VolumeInfo.TYPE_PRIVATE)) { 146 return vol; 147 } 148 } 149 return null; 150 } 151 152 @Override onSaveInstanceState(@onNull Bundle outState)153 protected void onSaveInstanceState(@NonNull Bundle outState) { 154 super.onSaveInstanceState(outState); 155 outState.putString(SAVE_STATE_FORMAT_PRIVATE_DISK_ID, mFormatAsPrivateDiskId); 156 outState.putString(SAVE_STATE_FORMAT_PUBLIC_DISK_ID, mFormatAsPublicDiskId); 157 outState.putString(SAVE_STATE_FORMAT_DISK_DESC, mFormatDiskDesc); 158 } 159 160 @VisibleForTesting handleFormatAsPrivateComplete(float privateBench, float internalBench)161 void handleFormatAsPrivateComplete(float privateBench, float internalBench) { 162 if (Math.abs(-1 - privateBench) < 0.1) { 163 final float frac = privateBench / internalBench; 164 Log.d(TAG, "New volume is " + frac + "x the speed of internal"); 165 166 // TODO: better threshold 167 if (privateBench > 2000000000) { 168 getFragmentManager().beginTransaction() 169 .replace(android.R.id.content, 170 SlowDriveStepFragment.newInstance()) 171 .commit(); 172 return; 173 } 174 } 175 launchMigrateStorageAndFinish(mFormatAsPrivateDiskId); 176 } 177 178 @VisibleForTesting 179 static class FormatReceiver extends BroadcastReceiver { 180 181 private final FormatActivity mActivity; 182 FormatReceiver(FormatActivity activity)183 FormatReceiver(FormatActivity activity) { 184 mActivity = activity; 185 } 186 187 @Override onReceive(Context context, Intent intent)188 public void onReceive(Context context, Intent intent) { 189 if (TextUtils.equals(intent.getAction(), 190 SettingsStorageService.ACTION_FORMAT_AS_PRIVATE) 191 && !TextUtils.isEmpty(mActivity.mFormatAsPrivateDiskId)) { 192 final String diskId = intent.getStringExtra(DiskInfo.EXTRA_DISK_ID); 193 if (TextUtils.equals(mActivity.mFormatAsPrivateDiskId, diskId)) { 194 final boolean success = 195 intent.getBooleanExtra(SettingsStorageService.EXTRA_SUCCESS, false); 196 if (success) { 197 if (mActivity.isResumed()) { 198 final float privateBench = intent.getLongExtra( 199 SettingsStorageService.EXTRA_PRIVATE_BENCH, -1); 200 final float internalBench = intent.getLongExtra( 201 SettingsStorageService.EXTRA_INTERNAL_BENCH, -1); 202 mActivity.handleFormatAsPrivateComplete(privateBench, internalBench); 203 } 204 205 Toast.makeText(context, mActivity.getString( 206 R.string.storage_format_success, mActivity.mFormatDiskDesc), 207 Toast.LENGTH_SHORT).show(); 208 } else { 209 Toast.makeText(context, 210 mActivity.getString(R.string.storage_format_failure, 211 mActivity.mFormatDiskDesc), 212 Toast.LENGTH_SHORT).show(); 213 mActivity.finish(); 214 } 215 } 216 } else if (TextUtils.equals(intent.getAction(), 217 SettingsStorageService.ACTION_FORMAT_AS_PUBLIC) 218 && !TextUtils.isEmpty(mActivity.mFormatAsPublicDiskId)) { 219 final String diskId = intent.getStringExtra(DiskInfo.EXTRA_DISK_ID); 220 if (TextUtils.equals(mActivity.mFormatAsPublicDiskId, diskId)) { 221 final boolean success = 222 intent.getBooleanExtra(SettingsStorageService.EXTRA_SUCCESS, false); 223 if (success) { 224 Toast.makeText(context, 225 mActivity.getString(R.string.storage_format_success, 226 mActivity.mFormatDiskDesc), Toast.LENGTH_SHORT).show(); 227 } else { 228 Toast.makeText(context, 229 mActivity.getString(R.string.storage_format_failure, 230 mActivity.mFormatDiskDesc), Toast.LENGTH_SHORT).show(); 231 } 232 mActivity.finish(); 233 } 234 } 235 } 236 } 237 238 @Override onRequestFormatAsPrivate(String diskId)239 public void onRequestFormatAsPrivate(String diskId) { 240 final FormattingProgressFragment fragment = FormattingProgressFragment.newInstance(); 241 getFragmentManager().beginTransaction() 242 .replace(android.R.id.content, fragment) 243 .commit(); 244 245 mFormatAsPrivateDiskId = diskId; 246 final List<VolumeInfo> volumes = mStorageManager.getVolumes(); 247 for (final VolumeInfo volume : volumes) { 248 if ((volume.getType() == VolumeInfo.TYPE_PRIVATE || 249 volume.getType() == VolumeInfo.TYPE_PUBLIC) && 250 TextUtils.equals(volume.getDiskId(), diskId)) { 251 mFormatDiskDesc = mStorageManager.getBestVolumeDescription(volume); 252 } 253 } 254 if (TextUtils.isEmpty(mFormatDiskDesc)) { 255 final DiskInfo info = mStorageManager.findDiskById(diskId); 256 if (info != null) { 257 mFormatDiskDesc = info.getDescription(); 258 } 259 } 260 SettingsStorageService.formatAsPrivate(this, diskId); 261 } 262 launchMigrateStorageAndFinish(String diskId)263 private void launchMigrateStorageAndFinish(String diskId) { 264 final List<VolumeInfo> candidates = 265 mPackageManager.getPrimaryStorageCandidateVolumes(); 266 VolumeInfo moveTarget = null; 267 for (final VolumeInfo candidate : candidates) { 268 if (TextUtils.equals(candidate.getDiskId(), diskId)) { 269 moveTarget = candidate; 270 break; 271 } 272 } 273 274 if (moveTarget != null) { 275 startActivity(MigrateStorageActivity.getLaunchIntent(this, moveTarget.getId(), true)); 276 } 277 278 finish(); 279 } 280 281 @Override onRequestFormatAsPublic(String diskId, String volumeId)282 public void onRequestFormatAsPublic(String diskId, String volumeId) { 283 final FormattingProgressFragment fragment = FormattingProgressFragment.newInstance(); 284 getFragmentManager().beginTransaction() 285 .replace(android.R.id.content, fragment) 286 .commit(); 287 288 mFormatAsPublicDiskId = diskId; 289 if (!TextUtils.isEmpty(volumeId)) { 290 final VolumeInfo info = mStorageManager.findVolumeById(volumeId); 291 if (info != null) { 292 mFormatDiskDesc = mStorageManager.getBestVolumeDescription(info); 293 } 294 } 295 if (TextUtils.isEmpty(mFormatDiskDesc)) { 296 final DiskInfo info = mStorageManager.findDiskById(diskId); 297 if (info != null) { 298 mFormatDiskDesc = info.getDescription(); 299 } 300 } 301 SettingsStorageService.formatAsPublic(this, diskId); 302 } 303 304 @Override onCancelFormatDialog()305 public void onCancelFormatDialog() { 306 finish(); 307 } 308 309 @Override onSlowDriveWarningComplete()310 public void onSlowDriveWarningComplete() { 311 launchMigrateStorageAndFinish(mFormatAsPrivateDiskId); 312 } 313 } 314