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