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