• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.os.storage.cts;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.os.ParcelFileDescriptor;
22 import android.util.Log;
23 
24 import androidx.test.platform.app.InstrumentationRegistry;
25 
26 import com.google.common.collect.Iterables;
27 
28 import java.io.BufferedReader;
29 import java.io.FileInputStream;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.nio.charset.StandardCharsets;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.TimeoutException;
37 import java.util.function.Supplier;
38 
39 final class StorageManagerHelper {
40 
41     private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
42     private static final long POLLING_SLEEP_MILLIS = 100;
43 
44     /**
45      * Creates a virtual disk that simulates SDCard on a device. It is
46      * mounted as a public visible disk.
47      * @return the volume name of the disk just created
48      * @throws Exception, if the volume could not be created
49      */
createSDCardVirtualDisk()50     static String createSDCardVirtualDisk() throws Exception {
51         return createDiskAndGetVolumeName(true);
52     }
53     /**
54      * Creates a virtual disk that simulates USB on a device. It is
55      * mounted as a public invisible disk.
56      * @return the volume name of the disk just created
57      * @throws Exception, if the volume could not be created
58      */
createUSBVirtualDisk()59     static String createUSBVirtualDisk() throws Exception {
60         return createDiskAndGetVolumeName(false);
61     }
62 
63     /**
64      * Removes the simulated disk
65      */
removeVirtualDisk()66     static void removeVirtualDisk() throws Exception {
67         executeShellCommand("sm set-virtual-disk false");
68         //sleep to make sure that it is unmounted
69         Thread.sleep(5000);
70     }
71 
72     /**
73      * Create a public volume for testing and only return the one newly created as the volumeName.
74      */
createDiskAndGetVolumeName(boolean visible)75     public static String createDiskAndGetVolumeName(boolean visible) throws Exception {
76         //remove any existing volume that was mounted before
77         removeVirtualDisk();
78         String existingPublicVolume = getPublicVolumeExcluding(null);
79         executeShellCommand("sm set-force-adoptable " + (visible ? "on" : "off"));
80         executeShellCommand("sm set-virtual-disk true");
81         Thread.sleep(10000);
82         pollForCondition(StorageManagerHelper::partitionDisks,
83                 "Could not create public volume in time");
84         return getPublicVolumeExcluding(existingPublicVolume);
85     }
86 
partitionDisks()87     private static boolean partitionDisks() {
88         try {
89             List<String> diskNames = executeShellCommand("sm list-disks");
90             if (!diskNames.isEmpty()) {
91                 executeShellCommand("sm partition " + Iterables.getLast(diskNames) + " public");
92                 return true;
93             }
94         } catch (Exception ignored) {
95             //ignored
96         }
97         return false;
98     }
99 
pollForCondition(Supplier<Boolean> condition, String errorMessage)100     private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)
101             throws Exception {
102         Thread.sleep(2000);
103         for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
104             if (condition.get()) {
105                 return;
106             }
107             Thread.sleep(POLLING_SLEEP_MILLIS);
108         }
109         throw new TimeoutException(errorMessage);
110     }
111 
getPublicVolumeExcluding(String excludingVolume)112     private static String getPublicVolumeExcluding(String excludingVolume) throws Exception {
113 
114         List<String> volumes = executeShellCommand("sm list-volumes");
115         // list volumes will result in something like
116         // private mounted null
117         // public:7,281 mounted 3080-17E8
118         // emulated;0 mounted null
119         // and we are interested in 3080-17E8
120         for (String volume: volumes) {
121             if (volume.contains("public")
122                     && (excludingVolume == null || !volume.contains(excludingVolume))) {
123                 //public:7,281 mounted 3080-17E8
124                 String[] splits = volume.split(" ");
125                 //Return the last snippet, that is 3080-17E8
126                 return splits[splits.length - 1];
127             }
128         }
129         return null;
130     }
131 
isAdoptableStorageSupported(Context context)132     public static boolean isAdoptableStorageSupported(Context context) throws Exception {
133         return hasAdoptableStorageFeature(context) || hasAdoptableStorageFstab();
134     }
135 
hasAdoptableStorageFstab()136     private static boolean hasAdoptableStorageFstab() throws Exception {
137         List<String> hasAdoptable = executeShellCommand("sm has-adoptable");
138         if (hasAdoptable.isEmpty()) {
139             return false;
140         }
141         return Boolean.parseBoolean(hasAdoptable.get(0).trim());
142     }
143 
hasAdoptableStorageFeature(Context context)144     private static boolean hasAdoptableStorageFeature(Context context) throws Exception {
145         return context.getPackageManager().hasSystemFeature(
146                 PackageManager.FEATURE_ADOPTABLE_STORAGE);
147     }
148 
executeShellCommand(String command)149     private static List<String> executeShellCommand(String command) throws Exception {
150         final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
151                 .getUiAutomation().executeShellCommand(command);
152         BufferedReader br = null;
153         try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
154             br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
155             String str = null;
156             List<String> output = new ArrayList<>();
157             while ((str = br.readLine()) != null) {
158                 output.add(str);
159             }
160             return output;
161         } finally {
162             if (br != null) {
163                 closeQuietly(br);
164             }
165             closeQuietly(pfd);
166         }
167     }
168 
closeQuietly(AutoCloseable closeable)169     private static void closeQuietly(AutoCloseable closeable) {
170         if (closeable != null) {
171 
172             try {
173                 closeable.close();
174             } catch (RuntimeException rethrown) {
175                 throw rethrown;
176             } catch (Exception ignored) {
177                 Log.w("StorageManagerHelper", ignored.getMessage());
178             }
179         }
180     }
181 }
182