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