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