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