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