1 /* 2 * Copyright (C) 2016 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.IntentService; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.storage.DiskInfo; 23 import android.os.storage.StorageManager; 24 import android.os.storage.VolumeInfo; 25 import android.text.TextUtils; 26 import android.util.Log; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 import androidx.annotation.VisibleForTesting; 31 import androidx.localbroadcastmanager.content.LocalBroadcastManager; 32 33 import java.util.List; 34 35 public class SettingsStorageService { 36 37 private static final String TAG = "SettingsStorageService"; 38 39 public static final String ACTION_FORMAT_AS_PUBLIC = 40 "com.android.tv.settings.device.storage.FORMAT_AS_PUBLIC"; 41 public static final String ACTION_FORMAT_AS_PRIVATE = 42 "com.android.tv.settings.device.storage.FORMAT_AS_PRIVATE"; 43 public static final String ACTION_UNMOUNT = "com.android.tv.settings.device.storage.UNMOUNT"; 44 45 public static final String EXTRA_SUCCESS = "com.android.tv.settings.device.storage.SUCCESS"; 46 public static final String EXTRA_INTERNAL_BENCH = 47 "com.android.tv.settings.device.storage.INTERNAL_BENCH"; 48 public static final String EXTRA_PRIVATE_BENCH = 49 "com.android.tv.settings.device.storage.PRIVATE_BENCH"; 50 formatAsPublic(Context context, String diskId)51 public static void formatAsPublic(Context context, String diskId) { 52 final Intent intent = new Intent(context, Impl.class); 53 intent.setAction(ACTION_FORMAT_AS_PUBLIC); 54 intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId); 55 context.startService(intent); 56 } 57 formatAsPrivate(Context context, String diskId)58 public static void formatAsPrivate(Context context, String diskId) { 59 final Intent intent = new Intent(context, Impl.class); 60 intent.setAction(ACTION_FORMAT_AS_PRIVATE); 61 intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId); 62 context.startService(intent); 63 } 64 unmount(Context context, String volumeId)65 public static void unmount(Context context, String volumeId) { 66 final Intent intent = new Intent(context, Impl.class); 67 intent.setAction(ACTION_UNMOUNT); 68 intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, volumeId); 69 context.startService(intent); 70 } 71 72 public static class Impl extends IntentService { 73 Impl()74 public Impl() { 75 super(Impl.class.getName()); 76 } 77 78 @Override onHandleIntent(@ullable Intent intent)79 protected void onHandleIntent(@Nullable Intent intent) { 80 final String action = intent.getAction(); 81 if (TextUtils.isEmpty(action)) { 82 throw new IllegalArgumentException("Empty action in intent: " + intent); 83 } 84 85 switch (action) { 86 case ACTION_FORMAT_AS_PUBLIC: { 87 final String diskId = intent.getStringExtra(DiskInfo.EXTRA_DISK_ID); 88 if (TextUtils.isEmpty(diskId)) { 89 throw new IllegalArgumentException( 90 "No disk ID specified for format as public: " + intent); 91 } 92 formatAsPublic(diskId); 93 break; 94 } 95 case ACTION_FORMAT_AS_PRIVATE: { 96 final String diskId = intent.getStringExtra(DiskInfo.EXTRA_DISK_ID); 97 if (TextUtils.isEmpty(diskId)) { 98 throw new IllegalArgumentException( 99 "No disk ID specified for format as public: " + intent); 100 } 101 formatAsPrivate(diskId); 102 break; 103 } 104 case ACTION_UNMOUNT: { 105 final String volumeId = intent.getStringExtra(VolumeInfo.EXTRA_VOLUME_ID); 106 if (TextUtils.isEmpty(volumeId)) { 107 throw new IllegalArgumentException("No volume ID specified for unmount: " 108 + intent); 109 } 110 unmount(volumeId); 111 break; 112 } 113 } 114 } 115 formatAsPublic(String diskId)116 private void formatAsPublic(String diskId) { 117 try { 118 final StorageManager storageManager = getSystemService(StorageManager.class); 119 final List<VolumeInfo> volumes = storageManager.getVolumes(); 120 for (final VolumeInfo volume : volumes) { 121 if (TextUtils.equals(diskId, volume.getDiskId()) && 122 volume.getType() == VolumeInfo.TYPE_PRIVATE) { 123 storageManager.forgetVolume(volume.getFsUuid()); 124 } 125 } 126 127 storageManager.partitionPublic(diskId); 128 129 sendLocalBroadcast( 130 new Intent(ACTION_FORMAT_AS_PUBLIC) 131 .putExtra(DiskInfo.EXTRA_DISK_ID, diskId) 132 .putExtra(EXTRA_SUCCESS, true)); 133 } catch (Exception e) { 134 Log.e(TAG, "Failed to format " + diskId, e); 135 sendLocalBroadcast( 136 new Intent(ACTION_FORMAT_AS_PUBLIC) 137 .putExtra(DiskInfo.EXTRA_DISK_ID, diskId) 138 .putExtra(EXTRA_SUCCESS, false)); 139 } 140 } 141 formatAsPrivate(String diskId)142 private void formatAsPrivate(String diskId) { 143 try { 144 final StorageManager storageManager = getSystemService(StorageManager.class); 145 storageManager.partitionPrivate(diskId); 146 final long internalBench = storageManager.benchmark("private"); 147 148 final VolumeInfo privateVol = findPrivateVolume(storageManager, diskId); 149 final long privateBench; 150 if (privateVol != null) { 151 privateBench = storageManager.benchmark(privateVol.getId()); 152 } else { 153 privateBench = -1; 154 } 155 sendLocalBroadcast( 156 new Intent(ACTION_FORMAT_AS_PRIVATE) 157 .putExtra(DiskInfo.EXTRA_DISK_ID, diskId) 158 .putExtra(EXTRA_INTERNAL_BENCH, internalBench) 159 .putExtra(EXTRA_PRIVATE_BENCH, privateBench) 160 .putExtra(EXTRA_SUCCESS, true)); 161 } catch (Exception e) { 162 Log.e(TAG, "Failed to format " + diskId, e); 163 sendLocalBroadcast( 164 new Intent(ACTION_FORMAT_AS_PRIVATE) 165 .putExtra(DiskInfo.EXTRA_DISK_ID, diskId) 166 .putExtra(EXTRA_SUCCESS, false)); 167 } 168 } 169 170 @Nullable findPrivateVolume(@onNull StorageManager storageManager, String diskId)171 private VolumeInfo findPrivateVolume(@NonNull StorageManager storageManager, 172 String diskId) { 173 final List<VolumeInfo> vols = storageManager.getVolumes(); 174 for (final VolumeInfo vol : vols) { 175 if (TextUtils.equals(diskId, vol.getDiskId()) 176 && (vol.getType() == VolumeInfo.TYPE_PRIVATE)) { 177 return vol; 178 } 179 } 180 return null; 181 } 182 unmount(String volumeId)183 private void unmount(String volumeId) { 184 try { 185 final long minTime = System.currentTimeMillis() + 3000; 186 187 final StorageManager storageManager = getSystemService(StorageManager.class); 188 final VolumeInfo volumeInfo = storageManager.findVolumeById(volumeId); 189 if (volumeInfo != null && volumeInfo.isMountedReadable()) { 190 Log.d(TAG, "Trying to unmount " + volumeId); 191 storageManager.unmount(volumeId); 192 } else { 193 Log.d(TAG, "Volume not found, skipping unmount"); 194 } 195 196 long waitTime = minTime - System.currentTimeMillis(); 197 while (waitTime > 0) { 198 try { 199 Thread.sleep(waitTime); 200 } catch (InterruptedException e) { 201 // Ignore 202 } 203 waitTime = minTime - System.currentTimeMillis(); 204 } 205 sendLocalBroadcast(new Intent(ACTION_UNMOUNT) 206 .putExtra(VolumeInfo.EXTRA_VOLUME_ID, volumeId) 207 .putExtra(EXTRA_SUCCESS, true)); 208 } catch (Exception e) { 209 Log.d(TAG, "Could not unmount", e); 210 sendLocalBroadcast(new Intent(ACTION_UNMOUNT) 211 .putExtra(VolumeInfo.EXTRA_VOLUME_ID, volumeId) 212 .putExtra(EXTRA_SUCCESS, false)); 213 } 214 } 215 216 @VisibleForTesting sendLocalBroadcast(Intent intent)217 void sendLocalBroadcast(Intent intent) { 218 LocalBroadcastManager.getInstance(this).sendBroadcast(intent); 219 } 220 } 221 } 222