• 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 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     }
359 
360     @GuardedBy("mLock")
reconcileSdkDataPackageDirs( int userId, boolean isCeData, PackageInfoHolder pmInfoHolder)361     private void reconcileSdkDataPackageDirs(
362             int userId, boolean isCeData, PackageInfoHolder pmInfoHolder) {
363 
364         final List<String> volumeUuids = getMountedVolumes();
365         for (int i = 0; i < volumeUuids.size(); i++) {
366             final String volumeUuid = volumeUuids.get(i);
367             final String rootDir = getSdkDataRootDirectory(volumeUuid, userId, isCeData);
368             final String[] sdkPackages = new File(rootDir).list();
369             if (sdkPackages == null) {
370                 continue;
371             }
372             // Now loop over package directories and remove the ones that are invalid
373             for (int j = 0; j < sdkPackages.length; j++) {
374                 final String packageName = sdkPackages[j];
375                 // Only consider installed packages which are not instrumented and either
376                 // not using sdk or on incorrect volume for destroying
377                 final int uid = pmInfoHolder.getUid(packageName);
378                 final boolean isInstrumented =
379                         mSdkSandboxManagerLocal.isInstrumentationRunning(packageName, uid);
380                 final boolean hasCorrectVolume =
381                         TextUtils.equals(volumeUuid, pmInfoHolder.getVolumeUuid(packageName));
382                 final boolean isInstalled = !pmInfoHolder.isUninstalled(packageName);
383                 final boolean usesSdk = pmInfoHolder.usesSdk(packageName);
384                 if (!isInstrumented && isInstalled && (!hasCorrectVolume || !usesSdk)) {
385                     destroySdkDataPackageDirectory(volumeUuid, userId, packageName, isCeData);
386                 }
387             }
388         }
389 
390         // Now loop over all installed packages and ensure all packages have sdk data directories
391         final Iterator<String> it = pmInfoHolder.getInstalledPackagesUsingSdks().iterator();
392         while (it.hasNext()) {
393             final String packageName = it.next();
394             final String volumeUuid = pmInfoHolder.getVolumeUuid(packageName);
395             // Verify if package dir contains a subdir for each sdk and a shared directory
396             final String packageDir = getSdkDataPackageDirectory(volumeUuid, userId, packageName,
397                     isCeData);
398             final SubDirectories subDirs = new SubDirectories(packageDir);
399             final Set<String> expectedSdkNames = pmInfoHolder.getSdksUsed(packageName);
400             if (subDirs.isValid(expectedSdkNames)) {
401                 continue;
402             }
403 
404             Log.i(TAG, "Reconciling missing package directory for: " + packageDir);
405             final int uid = pmInfoHolder.getUid(packageName);
406             if (uid == -1) {
407                 Log.w(TAG, "Failed to get uid for reconcilation of " + packageDir);
408                 // Safe to continue since we will retry during loading sdk
409                 continue;
410             }
411             final CallingInfo callingInfo = new CallingInfo(uid, packageName);
412             reconcileSdkDataSubDirs(callingInfo, /*forInstrumentation=*/ false);
413         }
414     }
415 
getPackageManager(int userId)416     private PackageManager getPackageManager(int userId) {
417         return mContext.createContextAsUser(UserHandle.of(userId), 0).getPackageManager();
418     }
419 
420     @GuardedBy("mLock")
destroySdkDataPackageDirectory( @ullable String volumeUuid, int userId, String packageName, boolean isCeData)421     private void destroySdkDataPackageDirectory(
422             @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) {
423         final Path packageDir =
424                 Paths.get(getSdkDataPackageDirectory(volumeUuid, userId, packageName, isCeData));
425         if (!Files.exists(packageDir)) {
426             return;
427         }
428 
429         Log.i(TAG, "Destroying sdk data package directory " + packageDir);
430 
431         // Even though system owns the package directory, the sub-directories are owned by sandbox.
432         // We first need to get rid of sub-directories.
433         try {
434             final int flag = isCeData
435                     ? PackageManagerLocal.FLAG_STORAGE_CE
436                     : PackageManagerLocal.FLAG_STORAGE_DE;
437             mPackageManagerLocal.reconcileSdkData(volumeUuid, packageName,
438                     Collections.emptyList(), userId, /*appId=*/-1, /*previousAppId=*/-1,
439                     /*seInfo=*/"default", flag);
440         } catch (Exception e) {
441             Log.e(TAG, "Failed to destroy sdk data on user unlock for userId: " + userId
442                     + " packageName: " + packageName +  " error: " + e.getMessage());
443         }
444 
445         // Now that the package directory is empty, we can delete it
446         try {
447             Files.delete(packageDir);
448         } catch (Exception e) {
449             Log.e(
450                     TAG,
451                     "Failed to destroy sdk data on user unlock for userId: "
452                             + userId
453                             + " packageName: "
454                             + packageName
455                             + " error: "
456                             + e.getMessage());
457         }
458     }
459 
getDataDirectory(@ullable String volumeUuid)460     private String getDataDirectory(@Nullable String volumeUuid) {
461         if (TextUtils.isEmpty(volumeUuid)) {
462             return mRootDir + "/data";
463         } else {
464             return mRootDir + "/mnt/expand/" + volumeUuid;
465         }
466     }
467 
getSdkDataRootDirectory( @ullable String volumeUuid, int userId, boolean isCeData)468     private String getSdkDataRootDirectory(
469             @Nullable String volumeUuid, int userId, boolean isCeData) {
470         return getDataDirectory(volumeUuid) + (isCeData ? "/misc_ce/" : "/misc_de/") + userId
471             + "/sdksandbox";
472     }
473 
474     /** Fetches the SDK data package directory based on the arguments */
getSdkDataPackageDirectory( @ullable String volumeUuid, int userId, String packageName, boolean isCeData)475     public String getSdkDataPackageDirectory(
476             @Nullable String volumeUuid, int userId, String packageName, boolean isCeData) {
477         return getSdkDataRootDirectory(volumeUuid, userId, isCeData) + "/" + packageName;
478     }
479 
480     /**
481      * Class representing collection of sub-directories used for sdk sandox storage
482      *
483      * <p>There are two kinds of sub-directories:
484      *
485      * <ul>
486      *   <li>Sdk sub-directory: belongs exclusively to individual sdk and has name <sdk>@random
487      *   <li>Internal sub-directory: not specific to a particular sdk. Can belong to other entities.
488      *       Typically has structure <name>#random. The only exception being shared storage which is
489      *       just named "shared".
490      * </ul>
491      *
492      * <p>This class helps in organizing the sdk-subdirectories in groups so that they are easier to
493      * process.
494      *
495      * @hide
496      */
497     public static class SubDirectories {
498 
499         public static final String SHARED_DIR = "shared";
500         public static final String SANDBOX_DIR = "sandbox";
501         static final ArraySet<String> INTERNAL_SUBDIRS =
502                 new ArraySet(Arrays.asList(SHARED_DIR, SANDBOX_DIR));
503 
504         private final String mBaseDir;
505         private final ArrayMap<String, String> mSdkSubDirs;
506         private final ArrayMap<String, String> mInternalSubDirs;
507         private boolean mHasUnknownSubDirs = false;
508 
509         /**
510          * Lists all the children of provided path and organizes them into sdk and internal group.
511          */
SubDirectories(String path)512         SubDirectories(String path) {
513             mBaseDir = path;
514             mSdkSubDirs = new ArrayMap<>();
515             mInternalSubDirs = new ArrayMap<>();
516 
517             final File parent = new File(path);
518             final String[] children = parent.list();
519             if (children == null) {
520                 return;
521             }
522             for (int i = 0; i < children.length; i++) {
523                 final String child = children[i];
524                 if (child.indexOf("@") != -1) {
525                     final String[] tokens = child.split("@");
526                     mSdkSubDirs.put(tokens[0], child);
527                 } else if (child.indexOf("#") != -1) {
528                     final String[] tokens = child.split("#");
529                     mInternalSubDirs.put(tokens[0], child);
530                 } else if (child.equals(SHARED_DIR)) {
531                     mInternalSubDirs.put(SHARED_DIR, SHARED_DIR);
532                 } else {
533                     mHasUnknownSubDirs = true;
534                 }
535             }
536         }
537 
538         /** Gets the sub-directory name of provided sdk with random suffix */
539         @Nullable
getSdkSubDir(String sdkName)540         public String getSdkSubDir(String sdkName) {
541             return getSdkSubDir(sdkName, /*fullPath=*/ false);
542         }
543 
544         /** Gets the full path of per-sdk storage with random suffix */
545         @Nullable
getSdkSubDir(String sdkName, boolean fullPath)546         public String getSdkSubDir(String sdkName, boolean fullPath) {
547             final String subDir = mSdkSubDirs.getOrDefault(sdkName, null);
548             if (subDir == null || !fullPath) return subDir;
549             return Paths.get(mBaseDir, subDir).toString();
550         }
551 
552         /** Gets the full path of internal storage directory with random suffix */
553         @Nullable
getInternalSubDir(String subDirName, boolean fullPath)554         public String getInternalSubDir(String subDirName, boolean fullPath) {
555             final String subDir = mInternalSubDirs.getOrDefault(subDirName, null);
556             if (subDir == null || !fullPath) return subDir;
557             return Paths.get(mBaseDir, subDir).toString();
558         }
559 
560         /**
561          * Provided a list of sdk names, verifies if the current collection of directories satisfies
562          * per-sdk and internal sub-directory requirements.
563          */
isValid(Set<String> expectedSdkNames)564         public boolean isValid(Set<String> expectedSdkNames) {
565             final boolean hasCorrectSdkSubDirs = mSdkSubDirs.keySet().equals(expectedSdkNames);
566             final boolean hasCorrectInternalSubDirs =
567                     mInternalSubDirs.keySet().equals(INTERNAL_SUBDIRS);
568             return hasCorrectSdkSubDirs && hasCorrectInternalSubDirs && !mHasUnknownSubDirs;
569         }
570 
571         /**
572          * Give the sdk names, generate sub-dir names for these sdks and sub-dirs for internal use.
573          *
574          * <p>Random suffix for existing directories are re-used.
575          */
generateSubDirNames(List<String> sdkNames)576         public List<String> generateSubDirNames(List<String> sdkNames) {
577             final List<String> result = new ArrayList<>();
578 
579             // Populate sub-dirs for internal use
580             for (int i = 0; i < INTERNAL_SUBDIRS.size(); i++) {
581                 final String subDirValue = INTERNAL_SUBDIRS.valueAt(i);
582                 final String subDirName = getOrGenerateInternalSubDir(subDirValue);
583                 result.add(subDirName);
584             }
585 
586             // Populate sub-dirs for per-sdk usage
587             for (int i = 0; i < sdkNames.size(); i++) {
588                 final String sdkName = sdkNames.get(i);
589                 final String subDirName = getOrGenerateSdkSubDir(sdkName);
590                 result.add(subDirName);
591             }
592 
593             return result;
594         }
595 
getSdkNames()596         public ArrayList<String> getSdkNames() {
597             ArrayList<String> sdkNames = new ArrayList<>();
598             for (int i = 0; i < mSdkSubDirs.size(); i++) {
599                 sdkNames.add(mSdkSubDirs.keyAt(i));
600             }
601             return sdkNames;
602         }
603 
getOrGenerateSdkSubDir(String sdkName)604         private String getOrGenerateSdkSubDir(String sdkName) {
605             final String subDir = getSdkSubDir(sdkName);
606             if (subDir != null) return subDir;
607             return sdkName + "@" + getRandomString();
608         }
609 
getOrGenerateInternalSubDir(String internalDirName)610         private String getOrGenerateInternalSubDir(String internalDirName) {
611             if (internalDirName.equals(SHARED_DIR)) {
612                 return SHARED_DIR;
613             }
614             final String subDir = mInternalSubDirs.getOrDefault(internalDirName, null);
615             if (subDir != null) return subDir;
616             return internalDirName + "#" + getRandomString();
617         }
618 
619         // Returns a random string.
getRandomString()620         private static String getRandomString() {
621             SecureRandom random = new SecureRandom();
622             byte[] bytes = new byte[16];
623             random.nextBytes(bytes);
624             return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
625         }
626     }
627 
628     private static class PackageInfoHolder {
629         private final Context mContext;
630         final ArrayMap<String, Set<String>> mPackagesWithSdks = new ArrayMap<>();
631         final ArrayMap<String, Integer> mPackageNameToUid = new ArrayMap<>();
632         final ArrayMap<String, String> mPackageNameToVolumeUuid = new ArrayMap<>();
633         final Set<String> mUninstalledPackages = new ArraySet<>();
634 
PackageInfoHolder(Context context, int userId)635         PackageInfoHolder(Context context, int userId) {
636             mContext = context.createContextAsUser(UserHandle.of(userId), 0);
637 
638             PackageManager pm = mContext.getPackageManager();
639             final List<PackageInfo> packageInfoList = pm.getInstalledPackages(
640                     PackageManager.GET_SHARED_LIBRARY_FILES);
641             final ArraySet<String> installedPackages = new ArraySet<>();
642 
643             for (int i = 0; i < packageInfoList.size(); i++) {
644                 final PackageInfo info = packageInfoList.get(i);
645                 installedPackages.add(info.packageName);
646                 final String volumeUuid =
647                         StorageUuuidConverter.convertToVolumeUuid(info.applicationInfo.storageUuid);
648                 mPackageNameToVolumeUuid.put(info.packageName, volumeUuid);
649                 mPackageNameToUid.put(info.packageName, info.applicationInfo.uid);
650 
651                 final List<String> sdksUsedNames =
652                         SdkSandboxStorageManager.getSdksUsed(info.applicationInfo);
653                 if (sdksUsedNames.isEmpty()) {
654                     continue;
655                 }
656                 mPackagesWithSdks.put(info.packageName, new ArraySet<>(sdksUsedNames));
657             }
658 
659             // If an app is uninstalled with DELETE_KEEP_DATA flag, we need to preserve its sdk
660             // data. For that, we need names of uninstalled packages.
661             final List<PackageInfo> allPackages =
662                     pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES);
663             for (int i = 0; i < allPackages.size(); i++) {
664                 final String packageName = allPackages.get(i).packageName;
665                 if (!installedPackages.contains(packageName)) {
666                     mUninstalledPackages.add(packageName);
667                 }
668             }
669         }
670 
isUninstalled(String packageName)671         public boolean isUninstalled(String packageName) {
672             return mUninstalledPackages.contains(packageName);
673         }
674 
getUid(String packageName)675         public int getUid(String packageName) {
676             return mPackageNameToUid.getOrDefault(packageName, -1);
677         }
678 
getInstalledPackagesUsingSdks()679         public Set<String> getInstalledPackagesUsingSdks() {
680             return mPackagesWithSdks.keySet();
681         }
682 
getSdksUsed(String packageName)683         public Set<String> getSdksUsed(String packageName) {
684             return mPackagesWithSdks.get(packageName);
685         }
686 
usesSdk(String packageName)687         public boolean usesSdk(String packageName) {
688             return mPackagesWithSdks.containsKey(packageName);
689         }
690 
getVolumeUuid(String packageName)691         public String getVolumeUuid(String packageName) {
692             return mPackageNameToVolumeUuid.get(packageName);
693         }
694     }
695 
696     // TODO(b/234023859): We will remove this class once the required APIs get unhidden
697     // The class below has been copied from StorageManager's convert logic
698     private static class StorageUuuidConverter {
699         private static final String FAT_UUID_PREFIX = "fafafafa-fafa-5afa-8afa-fafa";
700         private static final UUID UUID_DEFAULT =
701                 UUID.fromString("41217664-9172-527a-b3d5-edabb50a7d69");
702         private static final String UUID_SYSTEM = "system";
703         private static final UUID UUID_SYSTEM_ =
704                 UUID.fromString("5d258386-e60d-59e3-826d-0089cdd42cc0");
705         private static final String UUID_PRIVATE_INTERNAL = null;
706         private static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
707         private static final UUID UUID_PRIMARY_PHYSICAL_ =
708                 UUID.fromString("0f95a519-dae7-5abf-9519-fbd6209e05fd");
709 
convertToVolumeUuid(@onNull UUID storageUuid)710         private static @Nullable String convertToVolumeUuid(@NonNull UUID storageUuid) {
711             if (UUID_DEFAULT.equals(storageUuid)) {
712                 return UUID_PRIVATE_INTERNAL;
713             } else if (UUID_PRIMARY_PHYSICAL_.equals(storageUuid)) {
714                 return UUID_PRIMARY_PHYSICAL;
715             } else if (UUID_SYSTEM_.equals(storageUuid)) {
716                 return UUID_SYSTEM;
717             } else {
718                 String uuidString = storageUuid.toString();
719                 // This prefix match will exclude fsUuids from private volumes because
720                 // (a) linux fsUuids are generally Version 4 (random) UUIDs so the prefix
721                 // will contain 4xxx instead of 5xxx and (b) we've already matched against
722                 // known namespace (Version 5) UUIDs above.
723                 if (uuidString.startsWith(FAT_UUID_PREFIX)) {
724                     String fatStr =
725                             uuidString.substring(FAT_UUID_PREFIX.length()).toUpperCase(Locale.US);
726                     return fatStr.substring(0, 4) + "-" + fatStr.substring(4);
727                 }
728 
729                 return storageUuid.toString();
730             }
731         }
732     }
733 
734     // We loop over "/mnt/expand" directory's children and find the volumeUuids
735     // TODO(b/234023859): We want to use storage manager api in future for this task
736     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
getMountedVolumes()737     List<String> getMountedVolumes() {
738         // Collect package names from root directory
739         final List<String> volumeUuids = new ArrayList<>();
740         volumeUuids.add(null);
741 
742         final String[] mountedVolumes = new File(mRootDir + "/mnt/expand").list();
743         if (mountedVolumes == null) {
744             return volumeUuids;
745         }
746 
747         for (int i = 0; i < mountedVolumes.length; i++) {
748             final String volumeUuid = mountedVolumes[i];
749             volumeUuids.add(volumeUuid);
750         }
751         return volumeUuids;
752     }
753 
getVolumeUuidForPackage(int userId, String packageName)754     private @Nullable String getVolumeUuidForPackage(int userId, String packageName)
755             throws PackageManager.NameNotFoundException {
756         PackageManager pm = getPackageManager(userId);
757         ApplicationInfo info = pm.getApplicationInfo(packageName, /*flags=*/ 0);
758         return StorageUuuidConverter.convertToVolumeUuid(info.storageUuid);
759     }
760 
761     /**
762      * Sdk data directories for a particular sdk or internal usage.
763      *
764      * <p>Every sdk sub-directory has two data directories. One is credentially encrypted storage
765      * and another is device encrypted.
766      *
767      * @hide
768      */
769     public static class StorageDirInfo {
770         @Nullable final String mCeData;
771         @Nullable final String mDeData;
772 
StorageDirInfo(@ullable String ceDataPath, @Nullable String deDataPath)773         public StorageDirInfo(@Nullable String ceDataPath, @Nullable String deDataPath) {
774             mCeData = ceDataPath;
775             mDeData = deDataPath;
776         }
777 
778         @Nullable
getCeDataDir()779         public String getCeDataDir() {
780             return mCeData;
781         }
782 
783         @Nullable
getDeDataDir()784         public String getDeDataDir() {
785             return mDeData;
786         }
787 
788         @Override
equals(Object o)789         public boolean equals(Object o) {
790             if (this == o) return true;
791             if (!(o instanceof StorageDirInfo)) return false;
792             StorageDirInfo that = (StorageDirInfo) o;
793             return mCeData.equals(that.mCeData) && mDeData.equals(that.mDeData);
794         }
795 
796         @Override
hashCode()797         public int hashCode() {
798             return Objects.hash(mCeData, mDeData);
799         }
800     }
801 }
802