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 com.android.server.sdksandbox;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.sdksandbox.LogUtil;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.SharedLibraryInfo;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.text.TextUtils;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.Base64;
33 import android.util.Log;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.server.pm.PackageManagerLocal;
38 
39 import java.io.File;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.security.SecureRandom;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collections;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Locale;
50 import java.util.Objects;
51 import java.util.Set;
52 import java.util.UUID;
53 
54 /**
55  * Helper class to handle all logics related to sdk data
56  *
57  * @hide
58  */
59 public class SdkSandboxStorageManager {
60     private static final String TAG = "SdkSandboxManager";
61 
62     private final Context mContext;
63     private final Object mLock = new Object();
64 
65     // Prefix to prepend with all sdk storage paths.
66     private final String mRootDir;
67 
68     private final SdkSandboxManagerLocal mSdkSandboxManagerLocal;
69     private final PackageManagerLocal mPackageManagerLocal;
70 
SdkSandboxStorageManager( Context context, SdkSandboxManagerLocal sdkSandboxManagerLocal, PackageManagerLocal packageManagerLocal)71     SdkSandboxStorageManager(
72             Context context,
73             SdkSandboxManagerLocal sdkSandboxManagerLocal,
74             PackageManagerLocal packageManagerLocal) {
75         this(context, sdkSandboxManagerLocal, packageManagerLocal, /*rootDir=*/ "");
76     }
77 
78     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
SdkSandboxStorageManager( Context context, SdkSandboxManagerLocal sdkSandboxManagerLocal, PackageManagerLocal packageManagerLocal, String rootDir)79     SdkSandboxStorageManager(
80             Context context,
81             SdkSandboxManagerLocal sdkSandboxManagerLocal,
82             PackageManagerLocal packageManagerLocal,
83             String rootDir) {
84         mContext = context;
85         mSdkSandboxManagerLocal = sdkSandboxManagerLocal;
86         mPackageManagerLocal = packageManagerLocal;
87         mRootDir = rootDir;
88     }
89 
notifyInstrumentationStarted(CallingInfo callingInfo)90     public void notifyInstrumentationStarted(CallingInfo callingInfo) {
91         synchronized (mLock) {
92             reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/true);
93         }
94     }
95 
96     /**
97      * Handle package added or updated event.
98      *
99      * <p>On package added or updated, we need to reconcile sdk subdirectories for the new/updated
100      * package.
101      */
onPackageAddedOrUpdated(CallingInfo callingInfo)102     public void onPackageAddedOrUpdated(CallingInfo callingInfo) {
103         LogUtil.d(TAG, "Preparing SDK data on package added or update for: " + callingInfo);
104         synchronized (mLock) {
105             reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/false);
106         }
107     }
108 
109     /**
110      * Handle user unlock event.
111      *
112      * When user unlocks their device, the credential encrypted storage becomes available for
113      * reconcilation.
114      */
onUserUnlocking(int userId)115     public void onUserUnlocking(int userId) {
116         synchronized (mLock) {
117             reconcileSdkDataPackageDirs(userId);
118         }
119     }
120 
prepareSdkDataOnLoad(CallingInfo callingInfo)121     public void prepareSdkDataOnLoad(CallingInfo callingInfo) {
122         LogUtil.d(TAG, "Preparing SDK data on load for: " + callingInfo);
123         synchronized (mLock) {
124             reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/false);
125         }
126     }
127 
getSdkStorageDirInfo(CallingInfo callingInfo, String sdkName)128     public StorageDirInfo getSdkStorageDirInfo(CallingInfo callingInfo, String sdkName) {
129         final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
130         if (packageDirInfo == null) {
131             // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
132             return new StorageDirInfo(null, null);
133         }
134         // TODO(b/232924025): We should have these information cached, instead of rescanning dirs.
135         synchronized (mLock) {
136             final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
137             final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
138             final String sdkCeSubDirPath = ceSubDirs.getSdkSubDir(sdkName, /*fullPath=*/ true);
139             final String sdkDeSubDirPath = deSubDirs.getSdkSubDir(sdkName, /*fullPath=*/ true);
140             return new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath);
141         }
142     }
143 
getSdkStorageDirInfo(CallingInfo callingInfo)144     public List<StorageDirInfo> getSdkStorageDirInfo(CallingInfo callingInfo) {
145         final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
146         if (packageDirInfo == null) {
147             // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
148             return new ArrayList<>();
149         }
150 
151         final List<StorageDirInfo> sdkStorageDirInfos = new ArrayList<>();
152 
153         synchronized (mLock) {
154             final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
155             final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
156 
157             /**
158              * Getting the SDKs name with deSubDir only assuming that ceSubDirs and deSubDirs have
159              * the same list of SDKs
160              */
161             final ArrayList<String> sdkNames = deSubDirs.getSdkNames();
162             int sdkNamesSize = sdkNames.size();
163 
164             for (int i = 0; i < sdkNamesSize; i++) {
165                 final String sdkCeSubDirPath =
166                         ceSubDirs.getSdkSubDir(sdkNames.get(i), /*fullPath=*/ true);
167                 final String sdkDeSubDirPath =
168                         deSubDirs.getSdkSubDir(sdkNames.get(i), /*fullPath=*/ true);
169                 sdkStorageDirInfos.add(new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath));
170             }
171             return sdkStorageDirInfos;
172         }
173     }
174 
getInternalStorageDirInfo(CallingInfo callingInfo, String subDirName)175     public StorageDirInfo getInternalStorageDirInfo(CallingInfo callingInfo, String subDirName) {
176         final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
177         if (packageDirInfo == null) {
178             // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
179             return new StorageDirInfo(null, null);
180         }
181         synchronized (mLock) {
182             final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
183             final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
184             final String ceSubDirPath = ceSubDirs.getInternalSubDir(subDirName, /*fullPath=*/ true);
185             final String deSubDirPath = deSubDirs.getInternalSubDir(subDirName, /*fullPath=*/ true);
186             return new StorageDirInfo(ceSubDirPath, deSubDirPath);
187         }
188     }
189 
getInternalStorageDirInfo(CallingInfo callingInfo)190     public List<StorageDirInfo> getInternalStorageDirInfo(CallingInfo callingInfo) {
191         final StorageDirInfo packageDirInfo = getSdkDataPackageDirInfo(callingInfo);
192         if (packageDirInfo == null) {
193             // TODO(b/238164644): SdkSandboxManagerService should fail loadSdk
194             return new ArrayList<>();
195         }
196 
197         final List<StorageDirInfo> internalStorageDirInfos = new ArrayList<>();
198 
199         synchronized (mLock) {
200             final SubDirectories ceSubDirs = new SubDirectories(packageDirInfo.getCeDataDir());
201             final SubDirectories deSubDirs = new SubDirectories(packageDirInfo.getDeDataDir());
202 
203             List<String> internalSubDirNames =
204                     Arrays.asList(SubDirectories.SHARED_DIR, SubDirectories.SANDBOX_DIR);
205 
206             for (int i = 0; i < 2; i++) {
207                 final String sdkCeSubDirPath =
208                         ceSubDirs.getInternalSubDir(internalSubDirNames.get(i), /*fullPath=*/ true);
209                 final String sdkDeSubDirPath =
210                         deSubDirs.getInternalSubDir(internalSubDirNames.get(i), /*fullPath=*/ true);
211                 internalStorageDirInfos.add(new StorageDirInfo(sdkCeSubDirPath, sdkDeSubDirPath));
212             }
213             return internalStorageDirInfos;
214         }
215     }
216 
217     @Nullable
getSdkDataPackageDirInfo(CallingInfo callingInfo)218     private StorageDirInfo getSdkDataPackageDirInfo(CallingInfo callingInfo) {
219         final int uid = callingInfo.getUid();
220         final String packageName = callingInfo.getPackageName();
221         String volumeUuid = null;
222         try {
223             volumeUuid = getVolumeUuidForPackage(getUserId(uid), packageName);
224         } catch (Exception e) {
225             Log.w(TAG, "Failed to find package " + packageName + " error: " + e.getMessage());
226             return null;
227         }
228         final String cePackagePath =
229                 getSdkDataPackageDirectory(
230                         volumeUuid, getUserId(uid), packageName, /*isCeData=*/ true);
231         final String dePackagePath =
232                 getSdkDataPackageDirectory(
233                         volumeUuid, getUserId(uid), packageName, /*isCeData=*/ false);
234         return new StorageDirInfo(cePackagePath, dePackagePath);
235     }
236 
getUserId(int uid)237     private int getUserId(int uid) {
238         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
239         return userHandle.getIdentifier();
240     }
241 
242     @GuardedBy("mLock")
reconcileSdkDataSubDirs(CallingInfo callingInfo, boolean forInstrumentation)243     private void reconcileSdkDataSubDirs(CallingInfo callingInfo, boolean forInstrumentation) {
244         final int uid = callingInfo.getUid();
245         final int userId = getUserId(uid);
246         final String packageName = callingInfo.getPackageName();
247         final List<String> sdksUsed = getSdksUsed(userId, packageName);
248         if (sdksUsed.isEmpty()) {
249             if (forInstrumentation) {
250                 Log.w(TAG,
251                         "Running instrumentation for the sdk-sandbox process belonging to client "
252                                 + "app "
253                                 + packageName + " (uid = " + uid
254                                 + "). However client app doesn't depend on any SDKs. Only "
255                                 + "creating \"shared\" sdk sandbox data sub directory");
256             } else {
257                 Log.i(TAG, "No SDKs used. Skipping SDK data reconcilation for " + callingInfo);
258                 return;
259             }
260         }
261         String volumeUuid = null;
262         try {
263             volumeUuid = getVolumeUuidForPackage(userId, packageName);
264         } catch (Exception e) {
265             Log.w(TAG, "Failed to find package " + packageName + " error: " + e.getMessage());
266             return;
267         }
268         final String deSdkDataPackagePath =
269                 getSdkDataPackageDirectory(volumeUuid, userId, packageName, /*isCeData=*/ false);
270         final SubDirectories existingDeSubDirs = new SubDirectories(deSdkDataPackagePath);
271 
272         final int appId = UserHandle.getAppId(uid);
273         final UserManager um = mContext.getSystemService(UserManager.class);
274         int flags = 0;
275         boolean doesCeNeedReconcile = false;
276         boolean doesDeNeedReconcile = false;
277         final Set<String> expectedSdkNames = new ArraySet<>(sdksUsed);
278         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
279         if (um.isUserUnlockingOrUnlocked(userHandle)) {
280             final String ceSdkDataPackagePath =
281                     getSdkDataPackageDirectory(volumeUuid, userId, packageName, /*isCeData=*/ true);
282             final SubDirectories ceSubDirsBeforeReconcilePrefix =
283                     new SubDirectories(ceSdkDataPackagePath);
284             flags = PackageManagerLocal.FLAG_STORAGE_CE | PackageManagerLocal.FLAG_STORAGE_DE;
285             doesCeNeedReconcile = !ceSubDirsBeforeReconcilePrefix.isValid(expectedSdkNames);
286             doesDeNeedReconcile = !existingDeSubDirs.isValid(expectedSdkNames);
287         } else {
288             flags = PackageManagerLocal.FLAG_STORAGE_DE;
289             doesDeNeedReconcile = !existingDeSubDirs.isValid(expectedSdkNames);
290         }
291 
292         // Reconcile only if ce or de subdirs are different than expectation
293         if (doesCeNeedReconcile || doesDeNeedReconcile) {
294             // List of all the sub-directories we need to create
295             final List<String> subDirNames = existingDeSubDirs.generateSubDirNames(sdksUsed);
296             try {
297                 // TODO(b/224719352): Pass actual seinfo from here
298                 mPackageManagerLocal.reconcileSdkData(
299                         volumeUuid,
300                         packageName,
301                         subDirNames,
302                         userId,
303                         appId,
304                         /*previousAppId=*/ -1,
305                         /*seInfo=*/ "default",
306                         flags);
307                 Log.i(TAG, "SDK data reconciled for " + callingInfo);
308             } catch (Exception e) {
309                 // We will retry when sdk gets loaded
310                 Log.w(TAG, "Failed to reconcileSdkData for " + packageName + " subDirNames: "
311                         + String.join(", ", subDirNames) + " error: " + e.getMessage());
312             }
313         } else {
314             Log.i(TAG, "Skipping SDK data reconcilation for " + callingInfo);
315         }
316     }
317 
318     /**
319      * Returns list of sdks {@code packageName} uses
320      */
321     @SuppressWarnings("MixedMutabilityReturnType")
getSdksUsed(int userId, String packageName)322     private List<String> getSdksUsed(int userId, String packageName) {
323         PackageManager pm = getPackageManager(userId);
324         try {
325             ApplicationInfo info = pm.getApplicationInfo(
326                     packageName, PackageManager.GET_SHARED_LIBRARY_FILES);
327             return getSdksUsed(info);
328         } catch (PackageManager.NameNotFoundException ignored) {
329             return Collections.emptyList();
330         }
331     }
332 
getSdksUsed(ApplicationInfo info)333     private static List<String> getSdksUsed(ApplicationInfo info) {
334         List<String> result = new ArrayList<>();
335         List<SharedLibraryInfo> sharedLibraries = info.getSharedLibraryInfos();
336         for (int i = 0; i < sharedLibraries.size(); i++) {
337             final SharedLibraryInfo sharedLib = sharedLibraries.get(i);
338             if (sharedLib.getType() != SharedLibraryInfo.TYPE_SDK_PACKAGE) {
339                 continue;
340             }
341             result.add(sharedLib.getName());
342         }
343         return result;
344     }
345 
346     /**
347      * For the given {@code userId}, ensure that sdk data package directories are still valid.
348      *
349      * <p>The primary concern of this method is to remove invalid data directories. Missing valid
350      * directories will get created when the app loads sdk for the first time.
351      */
352     @GuardedBy("mLock")
reconcileSdkDataPackageDirs(int userId)353     private void reconcileSdkDataPackageDirs(int userId) {
354         Log.i(TAG, "Reconciling sdk data package directories for " + userId);
355         PackageInfoHolder pmInfoHolder = new PackageInfoHolder(mContext, userId);
356         reconcileSdkDataPackageDirs(userId, /*isCeData=*/ true, pmInfoHolder);
357         reconcileSdkDataPackageDirs(userId, /*isCeData=*/ false, pmInfoHolder);
358         LogUtil.d(TAG, "Reconciliation of sdk data package directories complete for " + userId);
359     }
360 
361     @GuardedBy("mLock")
reconcileSdkDataPackageDirs( int userId, boolean isCeData, PackageInfoHolder pmInfoHolder)362     private void reconcileSdkDataPackageDirs(
363             int userId, boolean isCeData, PackageInfoHolder pmInfoHolder) {
364 
365         final List<String> volumeUuids = getMountedVolumes();
366         for (int i = 0; i < volumeUuids.size(); i++) {
367             final String volumeUuid = volumeUuids.get(i);
368             final String rootDir = getSdkDataRootDirectory(volumeUuid, userId, isCeData);
369             final String[] sdkPackages = new File(rootDir).list();
370             if (sdkPackages == null) {
371                 continue;
372             }
373             // Now loop over package directories and remove the ones that are invalid
374             for (int j = 0; j < sdkPackages.length; j++) {
375                 final String packageName = sdkPackages[j];
376                 // Only consider installed packages which are not instrumented and either
377                 // not using sdk or on incorrect volume for destroying
378                 final int uid = pmInfoHolder.getUid(packageName);
379                 final boolean isInstrumented =
380                         mSdkSandboxManagerLocal.isInstrumentationRunning(packageName, uid);
381                 final boolean hasCorrectVolume =
382                         TextUtils.equals(volumeUuid, pmInfoHolder.getVolumeUuid(packageName));
383                 final boolean isInstalled = !pmInfoHolder.isUninstalled(packageName);
384                 final boolean usesSdk = pmInfoHolder.usesSdk(packageName);
385                 if (!isInstrumented && isInstalled && (!hasCorrectVolume || !usesSdk)) {
386                     destroySdkDataPackageDirectory(volumeUuid, userId, packageName, isCeData);
387                 }
388             }
389         }
390 
391         // Now loop over all installed packages and ensure all packages have sdk data directories
392         final Iterator<String> it = pmInfoHolder.getInstalledPackagesUsingSdks().iterator();
393         while (it.hasNext()) {
394             final String packageName = it.next();
395             final String volumeUuid = pmInfoHolder.getVolumeUuid(packageName);
396             // Verify if package dir contains a subdir for each sdk and a shared directory
397             final String packageDir = getSdkDataPackageDirectory(volumeUuid, userId, packageName,
398                     isCeData);
399             final SubDirectories subDirs = new SubDirectories(packageDir);
400             final Set<String> expectedSdkNames = pmInfoHolder.getSdksUsed(packageName);
401             if (subDirs.isValid(expectedSdkNames)) {
402                 continue;
403             }
404 
405             Log.i(TAG, "Reconciling missing package directory for: " + packageDir);
406             final int uid = pmInfoHolder.getUid(packageName);
407             if (uid == -1) {
408                 Log.w(TAG, "Failed to get uid for reconcilation of " + packageDir);
409                 // Safe to continue since we will retry during loading sdk
410                 continue;
411             }
412             final CallingInfo callingInfo = new CallingInfo(uid, packageName);
413             reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/ false);
414         }
415     }
416 
getPackageManager(int userId)417     private PackageManager getPackageManager(int userId) {
418         return mContext.createContextAsUser(UserHandle.of(userId), 0).getPackageManager();
419     }
420 
421     @GuardedBy("mLock")
destroySdkDataPackageDirectory( @ullable String volumeUuid, int userId, String packageName, boolean isCeData)422     private void destroySdkDataPackageDirectory(
423             @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) {
424         final Path packageDir =
425                 Paths.get(getSdkDataPackageDirectory(volumeUuid, userId, packageName, isCeData));
426         if (!Files.exists(packageDir)) {
427             return;
428         }
429 
430         Log.i(TAG, "Destroying sdk data package directory " + packageDir);
431 
432         // Even though system owns the package directory, the sub-directories are owned by sandbox.
433         // We first need to get rid of sub-directories.
434         try {
435             final int flag = isCeData
436                     ? PackageManagerLocal.FLAG_STORAGE_CE
437                     : PackageManagerLocal.FLAG_STORAGE_DE;
438             mPackageManagerLocal.reconcileSdkData(volumeUuid, packageName,
439                     Collections.emptyList(), userId, /*appId=*/-1, /*previousAppId=*/-1,
440                     /*seInfo=*/"default", flag);
441         } catch (Exception e) {
442             Log.e(TAG, "Failed to destroy sdk data on user unlock for userId: " + userId
443                     + " packageName: " + packageName +  " error: " + e.getMessage());
444         }
445 
446         // Now that the package directory is empty, we can delete it
447         try {
448             Files.delete(packageDir);
449         } catch (Exception e) {
450             Log.e(
451                     TAG,
452                     "Failed to destroy sdk data on user unlock for userId: "
453                             + userId
454                             + " packageName: "
455                             + packageName
456                             + " error: "
457                             + e.getMessage());
458         }
459     }
460 
getDataDirectory(@ullable String volumeUuid)461     private String getDataDirectory(@Nullable String volumeUuid) {
462         if (TextUtils.isEmpty(volumeUuid)) {
463             return mRootDir + "/data";
464         } else {
465             return mRootDir + "/mnt/expand/" + volumeUuid;
466         }
467     }
468 
getSdkDataRootDirectory( @ullable String volumeUuid, int userId, boolean isCeData)469     private String getSdkDataRootDirectory(
470             @Nullable String volumeUuid, int userId, boolean isCeData) {
471         return getDataDirectory(volumeUuid) + (isCeData ? "/misc_ce/" : "/misc_de/") + userId
472             + "/sdksandbox";
473     }
474 
475     /** Fetches the SDK data package directory based on the arguments */
getSdkDataPackageDirectory( @ullable String volumeUuid, int userId, String packageName, boolean isCeData)476     public String getSdkDataPackageDirectory(
477             @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) {
478         return getSdkDataRootDirectory(volumeUuid, userId, isCeData) + "/" + packageName;
479     }
480 
481     /**
482      * Class representing collection of sub-directories used for sdk sandox storage
483      *
484      * <p>There are two kinds of sub-directories:
485      *
486      * <ul>
487      *   <li>Sdk sub-directory: belongs exclusively to individual sdk and has name <sdk>@random
488      *   <li>Internal sub-directory: not specific to a particular sdk. Can belong to other entities.
489      *       Typically has structure <name>#random. The only exception being shared storage which is
490      *       just named "shared".
491      * </ul>
492      *
493      * <p>This class helps in organizing the sdk-subdirectories in groups so that they are easier to
494      * process.
495      *
496      * @hide
497      */
498     public static class SubDirectories {
499 
500         public static final String SHARED_DIR = "shared";
501         public static final String SANDBOX_DIR = "sandbox";
502         static final ArraySet<String> INTERNAL_SUBDIRS =
503                 new ArraySet(Arrays.asList(SHARED_DIR, SANDBOX_DIR));
504 
505         private final String mBaseDir;
506         private final ArrayMap<String, String> mSdkSubDirs;
507         private final ArrayMap<String, String> mInternalSubDirs;
508         private boolean mHasUnknownSubDirs = false;
509 
510         /**
511          * Lists all the children of provided path and organizes them into sdk and internal group.
512          */
SubDirectories(String path)513         SubDirectories(String path) {
514             mBaseDir = path;
515             mSdkSubDirs = new ArrayMap<>();
516             mInternalSubDirs = new ArrayMap<>();
517 
518             final File parent = new File(path);
519             final String[] children = parent.list();
520             if (children == null) {
521                 return;
522             }
523             for (int i = 0; i < children.length; i++) {
524                 final String child = children[i];
525                 if (child.indexOf("@") != -1) {
526                     final String[] tokens = child.split("@");
527                     mSdkSubDirs.put(tokens[0], child);
528                 } else if (child.indexOf("#") != -1) {
529                     final String[] tokens = child.split("#");
530                     mInternalSubDirs.put(tokens[0], child);
531                 } else if (child.equals(SHARED_DIR)) {
532                     mInternalSubDirs.put(SHARED_DIR, SHARED_DIR);
533                 } else {
534                     mHasUnknownSubDirs = true;
535                 }
536             }
537         }
538 
539         /** Gets the sub-directory name of provided sdk with random suffix */
540         @Nullable
getSdkSubDir(String sdkName)541         public String getSdkSubDir(String sdkName) {
542             return getSdkSubDir(sdkName, /*fullPath=*/ false);
543         }
544 
545         /** Gets the full path of per-sdk storage with random suffix */
546         @Nullable
getSdkSubDir(String sdkName, boolean fullPath)547         public String getSdkSubDir(String sdkName, boolean fullPath) {
548             final String subDir = mSdkSubDirs.getOrDefault(sdkName, null);
549             if (subDir == null || !fullPath) return subDir;
550             return Paths.get(mBaseDir, subDir).toString();
551         }
552 
553         /** Gets the full path of internal storage directory with random suffix */
554         @Nullable
getInternalSubDir(String subDirName, boolean fullPath)555         public String getInternalSubDir(String subDirName, boolean fullPath) {
556             final String subDir = mInternalSubDirs.getOrDefault(subDirName, null);
557             if (subDir == null || !fullPath) return subDir;
558             return Paths.get(mBaseDir, subDir).toString();
559         }
560 
561         /**
562          * Provided a list of sdk names, verifies if the current collection of directories satisfies
563          * per-sdk and internal sub-directory requirements.
564          */
isValid(Set<String> expectedSdkNames)565         public boolean isValid(Set<String> expectedSdkNames) {
566             final boolean hasCorrectSdkSubDirs = mSdkSubDirs.keySet().equals(expectedSdkNames);
567             final boolean hasCorrectInternalSubDirs =
568                     mInternalSubDirs.keySet().equals(INTERNAL_SUBDIRS);
569             return hasCorrectSdkSubDirs && hasCorrectInternalSubDirs && !mHasUnknownSubDirs;
570         }
571 
572         /**
573          * Give the sdk names, generate sub-dir names for these sdks and sub-dirs for internal use.
574          *
575          * <p>Random suffix for existing directories are re-used.
576          */
generateSubDirNames(List<String> sdkNames)577         public List<String> generateSubDirNames(List<String> sdkNames) {
578             final List<String> result = new ArrayList<>();
579 
580             // Populate sub-dirs for internal use
581             for (int i = 0; i < INTERNAL_SUBDIRS.size(); i++) {
582                 final String subDirValue = INTERNAL_SUBDIRS.valueAt(i);
583                 final String subDirName = getOrGenerateInternalSubDir(subDirValue);
584                 result.add(subDirName);
585             }
586 
587             // Populate sub-dirs for per-sdk usage
588             for (int i = 0; i < sdkNames.size(); i++) {
589                 final String sdkName = sdkNames.get(i);
590                 final String subDirName = getOrGenerateSdkSubDir(sdkName);
591                 result.add(subDirName);
592             }
593 
594             return result;
595         }
596 
getSdkNames()597         public ArrayList<String> getSdkNames() {
598             ArrayList<String> sdkNames = new ArrayList<>();
599             for (int i = 0; i < mSdkSubDirs.size(); i++) {
600                 sdkNames.add(mSdkSubDirs.keyAt(i));
601             }
602             return sdkNames;
603         }
604 
getOrGenerateSdkSubDir(String sdkName)605         private String getOrGenerateSdkSubDir(String sdkName) {
606             final String subDir = getSdkSubDir(sdkName);
607             if (subDir != null) return subDir;
608             return sdkName + "@" + getRandomString();
609         }
610 
getOrGenerateInternalSubDir(String internalDirName)611         private String getOrGenerateInternalSubDir(String internalDirName) {
612             if (internalDirName.equals(SHARED_DIR)) {
613                 return SHARED_DIR;
614             }
615             final String subDir = mInternalSubDirs.getOrDefault(internalDirName, null);
616             if (subDir != null) return subDir;
617             return internalDirName + "#" + getRandomString();
618         }
619 
620         // Returns a random string.
getRandomString()621         private static String getRandomString() {
622             SecureRandom random = new SecureRandom();
623             byte[] bytes = new byte[16];
624             random.nextBytes(bytes);
625             return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
626         }
627     }
628 
629     private static class PackageInfoHolder {
630         private final Context mContext;
631         final ArrayMap<String, Set<String>> mPackagesWithSdks = new ArrayMap<>();
632         final ArrayMap<String, Integer> mPackageNameToUid = new ArrayMap<>();
633         final ArrayMap<String, String> mPackageNameToVolumeUuid = new ArrayMap<>();
634         final Set<String> mUninstalledPackages = new ArraySet<>();
635 
PackageInfoHolder(Context context, int userId)636         PackageInfoHolder(Context context, int userId) {
637             mContext = context.createContextAsUser(UserHandle.of(userId), 0);
638 
639             PackageManager pm = mContext.getPackageManager();
640             final List<PackageInfo> packageInfoList = pm.getInstalledPackages(
641                     PackageManager.GET_SHARED_LIBRARY_FILES);
642             final ArraySet<String> installedPackages = new ArraySet<>();
643 
644             for (int i = 0; i < packageInfoList.size(); i++) {
645                 final PackageInfo info = packageInfoList.get(i);
646                 installedPackages.add(info.packageName);
647                 final String volumeUuid =
648                         StorageUuuidConverter.convertToVolumeUuid(info.applicationInfo.storageUuid);
649                 mPackageNameToVolumeUuid.put(info.packageName, volumeUuid);
650                 mPackageNameToUid.put(info.packageName, info.applicationInfo.uid);
651 
652                 final List<String> sdksUsedNames =
653                         SdkSandboxStorageManager.getSdksUsed(info.applicationInfo);
654                 if (sdksUsedNames.isEmpty()) {
655                     continue;
656                 }
657                 mPackagesWithSdks.put(info.packageName, new ArraySet<>(sdksUsedNames));
658             }
659 
660             // If an app is uninstalled with DELETE_KEEP_DATA flag, we need to preserve its sdk
661             // data. For that, we need names of uninstalled packages.
662             final List<PackageInfo> allPackages =
663                     pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES);
664             for (int i = 0; i < allPackages.size(); i++) {
665                 final String packageName = allPackages.get(i).packageName;
666                 if (!installedPackages.contains(packageName)) {
667                     mUninstalledPackages.add(packageName);
668                 }
669             }
670         }
671 
isUninstalled(String packageName)672         public boolean isUninstalled(String packageName) {
673             return mUninstalledPackages.contains(packageName);
674         }
675 
getUid(String packageName)676         public int getUid(String packageName) {
677             return mPackageNameToUid.getOrDefault(packageName, -1);
678         }
679 
getInstalledPackagesUsingSdks()680         public Set<String> getInstalledPackagesUsingSdks() {
681             return mPackagesWithSdks.keySet();
682         }
683 
getSdksUsed(String packageName)684         public Set<String> getSdksUsed(String packageName) {
685             return mPackagesWithSdks.get(packageName);
686         }
687 
usesSdk(String packageName)688         public boolean usesSdk(String packageName) {
689             return mPackagesWithSdks.containsKey(packageName);
690         }
691 
getVolumeUuid(String packageName)692         public String getVolumeUuid(String packageName) {
693             return mPackageNameToVolumeUuid.get(packageName);
694         }
695     }
696 
697     // TODO(b/234023859): We will remove this class once the required APIs get unhidden
698     // The class below has been copied from StorageManager's convert logic
699     private static class StorageUuuidConverter {
700         private static final String FAT_UUID_PREFIX = "fafafafa-fafa-5afa-8afa-fafa";
701         private static final UUID UUID_DEFAULT =
702                 UUID.fromString("41217664-9172-527a-b3d5-edabb50a7d69");
703         private static final String UUID_SYSTEM = "system";
704         private static final UUID UUID_SYSTEM_ =
705                 UUID.fromString("5d258386-e60d-59e3-826d-0089cdd42cc0");
706         private static final String UUID_PRIVATE_INTERNAL = null;
707         private static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
708         private static final UUID UUID_PRIMARY_PHYSICAL_ =
709                 UUID.fromString("0f95a519-dae7-5abf-9519-fbd6209e05fd");
710 
convertToVolumeUuid(@onNull UUID storageUuid)711         private static @Nullable String convertToVolumeUuid(@NonNull UUID storageUuid) {
712             if (UUID_DEFAULT.equals(storageUuid)) {
713                 return UUID_PRIVATE_INTERNAL;
714             } else if (UUID_PRIMARY_PHYSICAL_.equals(storageUuid)) {
715                 return UUID_PRIMARY_PHYSICAL;
716             } else if (UUID_SYSTEM_.equals(storageUuid)) {
717                 return UUID_SYSTEM;
718             } else {
719                 String uuidString = storageUuid.toString();
720                 // This prefix match will exclude fsUuids from private volumes because
721                 // (a) linux fsUuids are generally Version 4 (random) UUIDs so the prefix
722                 // will contain 4xxx instead of 5xxx and (b) we've already matched against
723                 // known namespace (Version 5) UUIDs above.
724                 if (uuidString.startsWith(FAT_UUID_PREFIX)) {
725                     String fatStr =
726                             uuidString.substring(FAT_UUID_PREFIX.length()).toUpperCase(Locale.US);
727                     return fatStr.substring(0, 4) + "-" + fatStr.substring(4);
728                 }
729 
730                 return storageUuid.toString();
731             }
732         }
733     }
734 
735     // We loop over "/mnt/expand" directory's children and find the volumeUuids
736     // TODO(b/234023859): We want to use storage manager api in future for this task
737     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
getMountedVolumes()738     List<String> getMountedVolumes() {
739         // Collect package names from root directory
740         final List<String> volumeUuids = new ArrayList<>();
741         volumeUuids.add(null);
742 
743         final String[] mountedVolumes = new File(mRootDir + "/mnt/expand").list();
744         if (mountedVolumes == null) {
745             return volumeUuids;
746         }
747 
748         for (int i = 0; i < mountedVolumes.length; i++) {
749             final String volumeUuid = mountedVolumes[i];
750             volumeUuids.add(volumeUuid);
751         }
752         return volumeUuids;
753     }
754 
getVolumeUuidForPackage(int userId, String packageName)755     private @Nullable String getVolumeUuidForPackage(int userId, String packageName)
756             throws PackageManager.NameNotFoundException {
757         PackageManager pm = getPackageManager(userId);
758         ApplicationInfo info = pm.getApplicationInfo(packageName, /*flags=*/ 0);
759         return StorageUuuidConverter.convertToVolumeUuid(info.storageUuid);
760     }
761 
762     /**
763      * Sdk data directories for a particular sdk or internal usage.
764      *
765      * <p>Every sdk sub-directory has two data directories. One is credentially encrypted storage
766      * and another is device encrypted.
767      *
768      * @hide
769      */
770     public static class StorageDirInfo {
771         @Nullable final String mCeData;
772         @Nullable final String mDeData;
773 
StorageDirInfo(@ullable String ceDataPath, @Nullable String deDataPath)774         public StorageDirInfo(@Nullable String ceDataPath, @Nullable String deDataPath) {
775             mCeData = ceDataPath;
776             mDeData = deDataPath;
777         }
778 
779         @Nullable
getCeDataDir()780         public String getCeDataDir() {
781             return mCeData;
782         }
783 
784         @Nullable
getDeDataDir()785         public String getDeDataDir() {
786             return mDeData;
787         }
788 
789         @Override
equals(Object o)790         public boolean equals(Object o) {
791             if (this == o) return true;
792             if (!(o instanceof StorageDirInfo)) return false;
793             StorageDirInfo that = (StorageDirInfo) o;
794             return mCeData.equals(that.mCeData) && mDeData.equals(that.mDeData);
795         }
796 
797         @Override
hashCode()798         public int hashCode() {
799             return Objects.hash(mCeData, mDeData);
800         }
801     }
802 }
803