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.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.os.Bundle;
25 import android.os.storage.DiskInfo;
26 import android.os.storage.StorageEventListener;
27 import android.os.storage.StorageManager;
28 import android.os.storage.VolumeInfo;
29 import android.os.storage.VolumeRecord;
30 import android.text.TextUtils;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.leanback.app.GuidedStepFragment;
35 import androidx.leanback.widget.GuidanceStylist;
36 import androidx.leanback.widget.GuidedAction;
37 
38 import com.android.tv.settings.R;
39 import com.android.tv.settings.device.StorageResetActivity;
40 
41 import java.util.List;
42 
43 public class NewStorageActivity extends Activity {
44 
45     private static final String TAG = "NewStorageActivity";
46 
47     private static final String ACTION_NEW_STORAGE =
48             "com.android.tv.settings.device.storage.NewStorageActivity.NEW_STORAGE";
49     private static final String ACTION_MISSING_STORAGE =
50             "com.android.tv.settings.device.storage.NewStorageActivity.MISSING_STORAGE";
51 
getNewStorageLaunchIntent(Context context, String volumeId, String diskId)52     public static Intent getNewStorageLaunchIntent(Context context, String volumeId,
53             String diskId) {
54         final Intent i = new Intent(context, NewStorageActivity.class);
55         i.setAction(ACTION_NEW_STORAGE);
56         i.putExtra(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
57         i.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
58         return i;
59     }
60 
getMissingStorageLaunchIntent(Context context, String fsUuid)61     public static Intent getMissingStorageLaunchIntent(Context context, String fsUuid) {
62         final Intent i = new Intent(context, NewStorageActivity.class);
63         i.setAction(ACTION_MISSING_STORAGE);
64         i.putExtra(VolumeRecord.EXTRA_FS_UUID, fsUuid);
65         return i;
66     }
67 
68     @Override
onCreate(@ullable Bundle savedInstanceState)69     protected void onCreate(@Nullable Bundle savedInstanceState) {
70         super.onCreate(savedInstanceState);
71 
72         if (savedInstanceState == null) {
73             final String action = getIntent().getAction();
74 
75             if (TextUtils.equals(action, ACTION_MISSING_STORAGE)) {
76                 // Launched from a notification, see com.android.systemui.usb.StorageNotification
77                 final String fsUuid = getIntent().getStringExtra(VolumeRecord.EXTRA_FS_UUID);
78                 if (TextUtils.isEmpty(fsUuid)) {
79                     throw new IllegalStateException(
80                             "NewStorageActivity launched without specifying missing storage");
81                 }
82 
83                 getFragmentManager().beginTransaction()
84                         .add(android.R.id.content, MissingStorageFragment.newInstance(fsUuid))
85                         .commit();
86             } else {
87                 final String volumeId = getIntent().getStringExtra(VolumeInfo.EXTRA_VOLUME_ID);
88                 final String diskId = getIntent().getStringExtra(DiskInfo.EXTRA_DISK_ID);
89                 if (TextUtils.isEmpty(volumeId) && TextUtils.isEmpty(diskId)) {
90                     throw new IllegalStateException(
91                             "NewStorageActivity launched without specifying new storage");
92                 }
93 
94                 getFragmentManager().beginTransaction()
95                         .add(android.R.id.content, NewStorageFragment.newInstance(volumeId, diskId))
96                         .commit();
97             }
98         }
99     }
100 
101     public static class NewStorageFragment extends GuidedStepFragment {
102 
103         private static final int ACTION_BROWSE = 1;
104         private static final int ACTION_FORMAT_AS_PRIVATE = 2;
105         private static final int ACTION_UNMOUNT = 3;
106         private static final int ACTION_FORMAT_AS_PUBLIC = 4;
107 
108         private String mVolumeId;
109         private String mDiskId;
110         private String mDescription;
111 
112         private final StorageEventListener mStorageEventListener = new StorageEventListener() {
113             @Override
114             public void onDiskDestroyed(DiskInfo disk) {
115                 checkForUnmount();
116             }
117 
118             @Override
119             public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
120                 checkForUnmount();
121             }
122         };
123 
newInstance(String volumeId, String diskId)124         public static NewStorageFragment newInstance(String volumeId, String diskId) {
125             final Bundle b = new Bundle(1);
126             b.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
127             b.putString(DiskInfo.EXTRA_DISK_ID, diskId);
128             final NewStorageFragment fragment = new NewStorageFragment();
129             fragment.setArguments(b);
130             return fragment;
131         }
132 
133         @Override
onCreate(Bundle savedInstanceState)134         public void onCreate(Bundle savedInstanceState) {
135             StorageManager storageManager = getActivity().getSystemService(StorageManager.class);
136             mVolumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
137             mDiskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID);
138             if (TextUtils.isEmpty(mVolumeId) && TextUtils.isEmpty(mDiskId)) {
139                 throw new IllegalStateException(
140                         "NewStorageFragment launched without specifying new storage");
141             }
142             if (!TextUtils.isEmpty(mVolumeId)) {
143                 final VolumeInfo info = storageManager.findVolumeById(mVolumeId);
144                 mDescription = storageManager.getBestVolumeDescription(info);
145                 mDiskId = info.getDiskId();
146             } else {
147                 final DiskInfo info = storageManager.findDiskById(mDiskId);
148                 mDescription = info.getDescription();
149             }
150 
151             super.onCreate(savedInstanceState);
152         }
153 
154         @Override
onStart()155         public void onStart() {
156             super.onStart();
157             checkForUnmount();
158             getActivity().getSystemService(StorageManager.class)
159                     .registerListener(mStorageEventListener);
160         }
161 
162         @Override
onStop()163         public void onStop() {
164             super.onStop();
165             getActivity().getSystemService(StorageManager.class)
166                     .unregisterListener(mStorageEventListener);
167         }
168 
169         @Override
onCreateGuidance(Bundle savedInstanceState)170         public @NonNull GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
171             return new GuidanceStylist.Guidance(
172                     getString(R.string.storage_new_title),
173                     mDescription,
174                     null,
175                     getActivity().getDrawable(R.drawable.ic_storage_132dp));
176         }
177 
178         @Override
onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)179         public void onCreateActions(@NonNull List<GuidedAction> actions,
180                 Bundle savedInstanceState) {
181             if (TextUtils.isEmpty(mVolumeId)) {
182                 actions.add(new GuidedAction.Builder(getContext())
183                         .title(R.string.storage_new_action_format_public)
184                         .id(ACTION_FORMAT_AS_PUBLIC)
185                         .build());
186             } else {
187                 actions.add(new GuidedAction.Builder(getContext())
188                         .title(R.string.storage_new_action_browse)
189                         .id(ACTION_BROWSE)
190                         .build());
191             }
192             actions.add(new GuidedAction.Builder(getContext())
193                     .title(R.string.storage_new_action_adopt)
194                     .id(ACTION_FORMAT_AS_PRIVATE)
195                     .build());
196             actions.add(new GuidedAction.Builder(getContext())
197                     .title(R.string.storage_new_action_eject)
198                     .id(ACTION_UNMOUNT)
199                     .build());
200         }
201 
202         @Override
onGuidedActionClicked(GuidedAction action)203         public void onGuidedActionClicked(GuidedAction action) {
204             switch ((int) action.getId()) {
205                 case ACTION_FORMAT_AS_PUBLIC:
206                     startActivity(FormatActivity.getFormatAsPublicIntent(getActivity(), mDiskId));
207                     break;
208                 case ACTION_BROWSE:
209                     startActivity(new Intent(getActivity(), StorageResetActivity.class));
210                     break;
211                 case ACTION_FORMAT_AS_PRIVATE:
212                     startActivity(FormatActivity.getFormatAsPrivateIntent(getActivity(), mDiskId));
213                     break;
214                 case ACTION_UNMOUNT:
215                     // If we've mounted a volume, eject it. Otherwise just treat eject as cancel
216                     if (!TextUtils.isEmpty(mVolumeId)) {
217                         startActivity(
218                                 UnmountActivity.getIntent(getActivity(), mVolumeId, mDescription));
219                     }
220                     break;
221             }
222             getActivity().finish();
223         }
224 
checkForUnmount()225         private void checkForUnmount() {
226             if (!isAdded()) {
227                 return;
228             }
229 
230             final StorageManager storageManager =
231                     getContext().getSystemService(StorageManager.class);
232 
233             if (!TextUtils.isEmpty(mDiskId)) {
234                 // If the disk disappears, assume we're done
235                 final List<DiskInfo> diskInfos = storageManager.getDisks();
236                 boolean found = false;
237                 for (DiskInfo diskInfo : diskInfos) {
238                     if (TextUtils.equals(diskInfo.getId(), mDiskId)) {
239                         found = true;
240                         break;
241                     }
242                 }
243                 if (!found) {
244                     getActivity().finish();
245                 }
246             } else if (!TextUtils.isEmpty(mVolumeId)) {
247                 final List<VolumeInfo> volumeInfos = storageManager.getVolumes();
248                 boolean found = false;
249                 for (VolumeInfo volumeInfo : volumeInfos) {
250                     if (TextUtils.equals(volumeInfo.getId(), mVolumeId)) {
251                         found = true;
252                         break;
253                     }
254                 }
255                 if (!found) {
256                     getActivity().finish();
257                 }
258             }
259         }
260     }
261 
262     public static class MissingStorageFragment extends GuidedStepFragment {
263 
264         private String mFsUuid;
265         private String mDescription;
266 
267         private final BroadcastReceiver mDiskReceiver = new BroadcastReceiver() {
268             @Override
269             public void onReceive(Context context, Intent intent) {
270                 if (TextUtils.equals(intent.getAction(), VolumeInfo.ACTION_VOLUME_STATE_CHANGED)) {
271                     checkForRemount();
272                 }
273             }
274         };
275 
newInstance(String fsUuid)276         public static MissingStorageFragment newInstance(String fsUuid) {
277             final MissingStorageFragment fragment = new MissingStorageFragment();
278             final Bundle b = new Bundle(1);
279             b.putString(VolumeRecord.EXTRA_FS_UUID, fsUuid);
280             fragment.setArguments(b);
281             return fragment;
282         }
283 
284         @Override
onCreate(Bundle savedInstanceState)285         public void onCreate(Bundle savedInstanceState) {
286             StorageManager storageManager = getActivity().getSystemService(StorageManager.class);
287             mFsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID);
288             if (TextUtils.isEmpty(mFsUuid)) {
289                 throw new IllegalStateException(
290                         "MissingStorageFragment launched without specifying missing storage");
291             }
292             final VolumeRecord volumeRecord = storageManager.findRecordByUuid(mFsUuid);
293             mDescription = volumeRecord.getNickname();
294 
295             super.onCreate(savedInstanceState);
296         }
297 
298         @Override
onStart()299         public void onStart() {
300             super.onStart();
301             getContext().registerReceiver(mDiskReceiver,
302                     new IntentFilter(VolumeInfo.ACTION_VOLUME_STATE_CHANGED));
303             checkForRemount();
304         }
305 
306         @Override
onStop()307         public void onStop() {
308             super.onStop();
309             getContext().unregisterReceiver(mDiskReceiver);
310         }
311 
312         @Override
onCreateGuidance(Bundle savedInstanceState)313         public @NonNull GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
314             return new GuidanceStylist.Guidance(
315                     getString(R.string.storage_missing_title, mDescription),
316                     getString(R.string.storage_missing_description),
317                     null,
318                     getActivity().getDrawable(R.drawable.ic_error_132dp));
319         }
320 
321         @Override
onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)322         public void onCreateActions(@NonNull List<GuidedAction> actions,
323                 Bundle savedInstanceState) {
324             actions.add(new GuidedAction.Builder(getContext())
325                     .clickAction(GuidedAction.ACTION_ID_OK)
326                     .build());
327         }
328 
329         @Override
onGuidedActionClicked(GuidedAction action)330         public void onGuidedActionClicked(GuidedAction action) {
331             getActivity().finish();
332         }
333 
checkForRemount()334         private void checkForRemount() {
335             if (!isAdded()) {
336                 return;
337             }
338 
339             final List<VolumeInfo> volumeInfos =
340                     getContext().getSystemService(StorageManager.class).getVolumes();
341 
342             for (final VolumeInfo info : volumeInfos) {
343                 if (!TextUtils.equals(info.getFsUuid(), mFsUuid)) {
344                     continue;
345                 }
346                 if (info.isMountedReadable()) {
347                     getActivity().finish();
348                 }
349             }
350         }
351     }
352 
353 }
354