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