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