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