1 /* 2 * Copyright (C) 2015 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.pm; 18 19 import android.content.Context; 20 import android.content.pm.EphemeralApplicationInfo; 21 import android.content.pm.PackageParser; 22 import android.content.pm.PackageUserState; 23 import android.graphics.Bitmap; 24 import android.graphics.BitmapFactory; 25 import android.graphics.Canvas; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.os.Binder; 29 import android.os.Environment; 30 import android.provider.Settings; 31 import android.util.AtomicFile; 32 import android.util.Slog; 33 import android.util.SparseArray; 34 import android.util.Xml; 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.internal.util.ArrayUtils; 37 import com.android.internal.util.XmlUtils; 38 import libcore.io.IoUtils; 39 import libcore.util.EmptyArray; 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 import org.xmlpull.v1.XmlSerializer; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileNotFoundException; 47 import java.io.FileOutputStream; 48 import java.io.IOException; 49 import java.nio.charset.StandardCharsets; 50 import java.security.MessageDigest; 51 import java.security.NoSuchAlgorithmException; 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.List; 55 import java.util.Set; 56 57 /** 58 * This class is a part of the package manager service that is responsible 59 * for managing data associated with ephemeral apps such as cached uninstalled 60 * ephemeral apps and ephemeral apps' cookies. 61 */ 62 class EphemeralApplicationRegistry { 63 private static final boolean DEBUG = false; 64 65 private static final boolean ENABLED = false; 66 67 private static final String LOG_TAG = "EphemeralAppRegistry"; 68 69 private static final long DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS = 70 DEBUG ? 60 * 1000L /* one min */ : 30 * 24 * 60 * 60 * 1000L; /* one month */ 71 72 private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); 73 74 private static final String EPHEMERAL_APPS_FOLDER = "ephemeral"; 75 private static final String EPHEMERAL_APP_ICON_FILE = "icon.png"; 76 private static final String EPHEMERAL_APP_COOKIE_FILE_PREFIX = "cookie_"; 77 private static final String EPHEMERAL_APP_COOKIE_FILE_SIFFIX = ".dat"; 78 private static final String EPHEMERAL_APP_METADATA_FILE = "metadata.xml"; 79 80 private static final String TAG_PACKAGE = "package"; 81 private static final String TAG_PERMS = "perms"; 82 private static final String TAG_PERM = "perm"; 83 84 private static final String ATTR_LABEL = "label"; 85 private static final String ATTR_NAME = "name"; 86 private static final String ATTR_GRANTED = "granted"; 87 88 private final PackageManagerService mService; 89 90 @GuardedBy("mService.mPackages") 91 private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps; 92 93 public EphemeralApplicationRegistry(PackageManagerService service) { 94 mService = service; 95 } 96 97 public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) { 98 if (!ENABLED) { 99 return EmptyArray.BYTE; 100 } 101 pruneUninstalledEphemeralAppsLPw(userId); 102 103 File cookieFile = peekEphemeralCookieFile(packageName, userId); 104 if (cookieFile != null && cookieFile.exists()) { 105 try { 106 return IoUtils.readFileAsByteArray(cookieFile.toString()); 107 } catch (IOException e) { 108 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile); 109 } 110 } 111 return null; 112 } 113 114 public boolean setEphemeralApplicationCookieLPw(String packageName, 115 byte[] cookie, int userId) { 116 if (!ENABLED) { 117 return false; 118 } 119 pruneUninstalledEphemeralAppsLPw(userId); 120 121 PackageParser.Package pkg = mService.mPackages.get(packageName); 122 if (pkg == null) { 123 return false; 124 } 125 126 if (!isValidCookie(mService.mContext, cookie)) { 127 return false; 128 } 129 130 File appDir = getEphemeralApplicationDir(pkg.packageName, userId); 131 if (!appDir.exists() && !appDir.mkdirs()) { 132 return false; 133 } 134 135 File cookieFile = computeEphemeralCookieFile(pkg, userId); 136 if (cookieFile.exists() && !cookieFile.delete()) { 137 return false; 138 } 139 140 try (FileOutputStream fos = new FileOutputStream(cookieFile)) { 141 fos.write(cookie, 0, cookie.length); 142 } catch (IOException e) { 143 Slog.w(LOG_TAG, "Error writing cookie file: " + cookieFile); 144 return false; 145 } 146 return true; 147 } 148 149 public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) { 150 if (!ENABLED) { 151 return null; 152 } 153 pruneUninstalledEphemeralAppsLPw(userId); 154 155 File iconFile = new File(getEphemeralApplicationDir(packageName, userId), 156 EPHEMERAL_APP_ICON_FILE); 157 if (iconFile.exists()) { 158 return BitmapFactory.decodeFile(iconFile.toString()); 159 } 160 return null; 161 } 162 163 public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) { 164 if (!ENABLED) { 165 return Collections.emptyList(); 166 } 167 pruneUninstalledEphemeralAppsLPw(userId); 168 169 List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId); 170 result.addAll(getUninstalledEphemeralApplicationsLPr(userId)); 171 return result; 172 } 173 174 public void onPackageInstalledLPw(PackageParser.Package pkg) { 175 if (!ENABLED) { 176 return; 177 } 178 PackageSetting ps = (PackageSetting) pkg.mExtras; 179 if (ps == null) { 180 return; 181 } 182 for (int userId : UserManagerService.getInstance().getUserIds()) { 183 pruneUninstalledEphemeralAppsLPw(userId); 184 185 // Ignore not installed apps 186 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) { 187 continue; 188 } 189 190 // Propagate permissions before removing any state 191 propagateEphemeralAppPermissionsIfNeeded(pkg, userId); 192 193 // Remove the in-memory state 194 if (mUninstalledEphemeralApps != null) { 195 List<UninstalledEphemeralAppState> uninstalledAppStates = 196 mUninstalledEphemeralApps.get(userId); 197 if (uninstalledAppStates != null) { 198 final int appCount = uninstalledAppStates.size(); 199 for (int i = 0; i < appCount; i++) { 200 UninstalledEphemeralAppState uninstalledAppState = 201 uninstalledAppStates.get(i); 202 if (uninstalledAppState.mEphemeralApplicationInfo 203 .getPackageName().equals(pkg.packageName)) { 204 uninstalledAppStates.remove(i); 205 break; 206 } 207 } 208 } 209 } 210 211 // Remove the on-disk state except the cookie 212 File ephemeralAppDir = getEphemeralApplicationDir(pkg.packageName, userId); 213 new File(ephemeralAppDir, EPHEMERAL_APP_METADATA_FILE).delete(); 214 new File(ephemeralAppDir, EPHEMERAL_APP_ICON_FILE).delete(); 215 216 // If app signature changed - wipe the cookie 217 File currentCookieFile = peekEphemeralCookieFile(pkg.packageName, userId); 218 if (currentCookieFile == null) { 219 continue; 220 } 221 File expectedCookeFile = computeEphemeralCookieFile(pkg, userId); 222 if (!currentCookieFile.equals(expectedCookeFile)) { 223 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName 224 + " changed - dropping cookie"); 225 currentCookieFile.delete(); 226 } 227 } 228 } 229 230 public void onPackageUninstalledLPw(PackageParser.Package pkg) { 231 if (!ENABLED) { 232 return; 233 } 234 if (pkg == null) { 235 return; 236 } 237 PackageSetting ps = (PackageSetting) pkg.mExtras; 238 if (ps == null) { 239 return; 240 } 241 for (int userId : UserManagerService.getInstance().getUserIds()) { 242 pruneUninstalledEphemeralAppsLPw(userId); 243 244 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) { 245 continue; 246 } 247 248 if (pkg.applicationInfo.isEphemeralApp()) { 249 // Add a record for an uninstalled ephemeral app 250 addUninstalledEphemeralAppLPw(pkg, userId); 251 } else { 252 // Deleting an app prunes all ephemeral state such as cookie 253 deleteDir(getEphemeralApplicationDir(pkg.packageName, userId)); 254 } 255 } 256 } 257 258 public void onUserRemovedLPw(int userId) { 259 if (!ENABLED) { 260 return; 261 } 262 if (mUninstalledEphemeralApps != null) { 263 mUninstalledEphemeralApps.remove(userId); 264 } 265 deleteDir(getEphemeralApplicationsDir(userId)); 266 } 267 268 private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) { 269 EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId); 270 if (uninstalledApp == null) { 271 return; 272 } 273 if (mUninstalledEphemeralApps == null) { 274 mUninstalledEphemeralApps = new SparseArray<>(); 275 } 276 List<UninstalledEphemeralAppState> uninstalledAppStates = 277 mUninstalledEphemeralApps.get(userId); 278 if (uninstalledAppStates == null) { 279 uninstalledAppStates = new ArrayList<>(); 280 mUninstalledEphemeralApps.put(userId, uninstalledAppStates); 281 } 282 UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState( 283 uninstalledApp, System.currentTimeMillis()); 284 uninstalledAppStates.add(uninstalledAppState); 285 286 writeUninstalledEphemeralAppMetadata(uninstalledApp, userId); 287 writeEphemeralApplicationIconLPw(pkg, userId); 288 } 289 290 private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) { 291 File appDir = getEphemeralApplicationDir(pkg.packageName, userId); 292 if (!appDir.exists()) { 293 return; 294 } 295 296 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager()); 297 298 final Bitmap bitmap; 299 if (icon instanceof BitmapDrawable) { 300 bitmap = ((BitmapDrawable) icon).getBitmap(); 301 } else { 302 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), 303 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 304 Canvas canvas = new Canvas(bitmap); 305 icon.draw(canvas); 306 } 307 308 File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId), 309 EPHEMERAL_APP_ICON_FILE); 310 311 try (FileOutputStream out = new FileOutputStream(iconFile)) { 312 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 313 } catch (Exception e) { 314 Slog.e(LOG_TAG, "Error writing ephemeral app icon", e); 315 } 316 } 317 318 private void pruneUninstalledEphemeralAppsLPw(int userId) { 319 final long maxCacheDurationMillis = Settings.Global.getLong( 320 mService.mContext.getContentResolver(), 321 Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS, 322 DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS); 323 324 // Prune in-memory state 325 if (mUninstalledEphemeralApps != null) { 326 List<UninstalledEphemeralAppState> uninstalledAppStates = 327 mUninstalledEphemeralApps.get(userId); 328 if (uninstalledAppStates != null) { 329 final int appCount = uninstalledAppStates.size(); 330 for (int j = appCount - 1; j >= 0; j--) { 331 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j); 332 final long elapsedCachingMillis = System.currentTimeMillis() 333 - uninstalledAppState.mTimestamp; 334 if (elapsedCachingMillis > maxCacheDurationMillis) { 335 uninstalledAppStates.remove(j); 336 } 337 } 338 if (uninstalledAppStates.isEmpty()) { 339 mUninstalledEphemeralApps.remove(userId); 340 } 341 } 342 } 343 344 // Prune on-disk state 345 File ephemeralAppsDir = getEphemeralApplicationsDir(userId); 346 if (!ephemeralAppsDir.exists()) { 347 return; 348 } 349 File[] files = ephemeralAppsDir.listFiles(); 350 if (files == null) { 351 return; 352 } 353 for (File ephemeralDir : files) { 354 if (!ephemeralDir.isDirectory()) { 355 continue; 356 } 357 358 File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE); 359 if (!metadataFile.exists()) { 360 continue; 361 } 362 363 final long elapsedCachingMillis = System.currentTimeMillis() 364 - metadataFile.lastModified(); 365 if (elapsedCachingMillis > maxCacheDurationMillis) { 366 deleteDir(ephemeralDir); 367 } 368 } 369 } 370 371 private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) { 372 List<EphemeralApplicationInfo> result = null; 373 374 final int packageCount = mService.mPackages.size(); 375 for (int i = 0; i < packageCount; i++) { 376 PackageParser.Package pkg = mService.mPackages.valueAt(i); 377 if (!pkg.applicationInfo.isEphemeralApp()) { 378 continue; 379 } 380 EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId); 381 if (info == null) { 382 continue; 383 } 384 if (result == null) { 385 result = new ArrayList<>(); 386 } 387 result.add(info); 388 } 389 390 return result; 391 } 392 393 private EphemeralApplicationInfo createEphemeralAppInfoForPackage( 394 PackageParser.Package pkg, int userId) { 395 PackageSetting ps = (PackageSetting) pkg.mExtras; 396 if (ps == null) { 397 return null; 398 } 399 PackageUserState userState = ps.readUserState(userId); 400 if (userState == null || !userState.installed || userState.hidden) { 401 return null; 402 } 403 404 String[] requestedPermissions = new String[pkg.requestedPermissions.size()]; 405 pkg.requestedPermissions.toArray(requestedPermissions); 406 407 Set<String> permissions = ps.getPermissionsState().getPermissions(userId); 408 String[] grantedPermissions = new String[permissions.size()]; 409 permissions.toArray(grantedPermissions); 410 411 return new EphemeralApplicationInfo(pkg.applicationInfo, 412 requestedPermissions, grantedPermissions); 413 } 414 415 private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) { 416 List<UninstalledEphemeralAppState> uninstalledAppStates = 417 getUninstalledEphemeralAppStatesLPr(userId); 418 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) { 419 return Collections.emptyList(); 420 } 421 422 List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>(); 423 final int stateCount = uninstalledAppStates.size(); 424 for (int i = 0; i < stateCount; i++) { 425 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i); 426 uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo); 427 } 428 return uninstalledApps; 429 } 430 431 private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) { 432 EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId); 433 if (appInfo == null) { 434 return; 435 } 436 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) { 437 return; 438 } 439 final long identity = Binder.clearCallingIdentity(); 440 try { 441 for (String grantedPermission : appInfo.getGrantedPermissions()) { 442 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId); 443 } 444 } finally { 445 Binder.restoreCallingIdentity(identity); 446 } 447 } 448 449 private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName, 450 int userId) { 451 if (mUninstalledEphemeralApps != null) { 452 List<UninstalledEphemeralAppState> uninstalledAppStates = 453 mUninstalledEphemeralApps.get(userId); 454 if (uninstalledAppStates != null) { 455 final int appCount = uninstalledAppStates.size(); 456 for (int i = 0; i < appCount; i++) { 457 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i); 458 if (uninstalledAppState.mEphemeralApplicationInfo 459 .getPackageName().equals(packageName)) { 460 return uninstalledAppState.mEphemeralApplicationInfo; 461 } 462 } 463 } 464 } 465 466 File metadataFile = new File(getEphemeralApplicationDir(packageName, userId), 467 EPHEMERAL_APP_METADATA_FILE); 468 UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile); 469 if (uninstalledAppState == null) { 470 return null; 471 } 472 473 return uninstalledAppState.mEphemeralApplicationInfo; 474 } 475 476 private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) { 477 List<UninstalledEphemeralAppState> uninstalledAppStates = null; 478 if (mUninstalledEphemeralApps != null) { 479 uninstalledAppStates = mUninstalledEphemeralApps.get(userId); 480 if (uninstalledAppStates != null) { 481 return uninstalledAppStates; 482 } 483 } 484 485 File ephemeralAppsDir = getEphemeralApplicationsDir(userId); 486 if (ephemeralAppsDir.exists()) { 487 File[] files = ephemeralAppsDir.listFiles(); 488 if (files != null) { 489 for (File ephemeralDir : files) { 490 if (!ephemeralDir.isDirectory()) { 491 continue; 492 } 493 File metadataFile = new File(ephemeralDir, 494 EPHEMERAL_APP_METADATA_FILE); 495 UninstalledEphemeralAppState uninstalledAppState = 496 parseMetadataFile(metadataFile); 497 if (uninstalledAppState == null) { 498 continue; 499 } 500 if (uninstalledAppStates == null) { 501 uninstalledAppStates = new ArrayList<>(); 502 } 503 uninstalledAppStates.add(uninstalledAppState); 504 } 505 } 506 } 507 508 if (uninstalledAppStates != null) { 509 if (mUninstalledEphemeralApps == null) { 510 mUninstalledEphemeralApps = new SparseArray<>(); 511 } 512 mUninstalledEphemeralApps.put(userId, uninstalledAppStates); 513 } 514 515 return uninstalledAppStates; 516 } 517 518 private static boolean isValidCookie(Context context, byte[] cookie) { 519 if (ArrayUtils.isEmpty(cookie)) { 520 return true; 521 } 522 return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes(); 523 } 524 525 private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) { 526 if (!metadataFile.exists()) { 527 return null; 528 } 529 FileInputStream in; 530 try { 531 in = new AtomicFile(metadataFile).openRead(); 532 } catch (FileNotFoundException fnfe) { 533 Slog.i(LOG_TAG, "No ephemeral metadata file"); 534 return null; 535 } 536 537 final File ephemeralDir = metadataFile.getParentFile(); 538 final long timestamp = metadataFile.lastModified(); 539 final String packageName = ephemeralDir.getName(); 540 541 try { 542 XmlPullParser parser = Xml.newPullParser(); 543 parser.setInput(in, StandardCharsets.UTF_8.name()); 544 return new UninstalledEphemeralAppState( 545 parseMetadata(parser, packageName), timestamp); 546 } catch (XmlPullParserException | IOException e) { 547 throw new IllegalStateException("Failed parsing ephemeral" 548 + " metadata file: " + metadataFile, e); 549 } finally { 550 IoUtils.closeQuietly(in); 551 } 552 } 553 554 private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) { 555 File appDir = getEphemeralApplicationDir(pkg.packageName, userId); 556 String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg) 557 + EPHEMERAL_APP_COOKIE_FILE_SIFFIX; 558 return new File(appDir, cookieFile); 559 } 560 561 private static File peekEphemeralCookieFile(String packageName, int userId) { 562 File appDir = getEphemeralApplicationDir(packageName, userId); 563 if (!appDir.exists()) { 564 return null; 565 } 566 for (File file : appDir.listFiles()) { 567 if (!file.isDirectory() 568 && file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX) 569 && file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) { 570 return file; 571 } 572 } 573 return null; 574 } 575 576 private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName) 577 throws IOException, XmlPullParserException { 578 final int outerDepth = parser.getDepth(); 579 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 580 if (TAG_PACKAGE.equals(parser.getName())) { 581 return parsePackage(parser, packageName); 582 } 583 } 584 return null; 585 } 586 587 private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName) 588 throws IOException, XmlPullParserException { 589 String label = parser.getAttributeValue(null, ATTR_LABEL); 590 591 List<String> outRequestedPermissions = new ArrayList<>(); 592 List<String> outGrantedPermissions = new ArrayList<>(); 593 594 final int outerDepth = parser.getDepth(); 595 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 596 if (TAG_PERMS.equals(parser.getName())) { 597 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions); 598 } 599 } 600 601 String[] requestedPermissions = new String[outRequestedPermissions.size()]; 602 outRequestedPermissions.toArray(requestedPermissions); 603 604 String[] grantedPermissions = new String[outGrantedPermissions.size()]; 605 outGrantedPermissions.toArray(grantedPermissions); 606 607 return new EphemeralApplicationInfo(packageName, label, 608 requestedPermissions, grantedPermissions); 609 } 610 611 private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions, 612 List<String> outGrantedPermissions) throws IOException, XmlPullParserException { 613 final int outerDepth = parser.getDepth(); 614 while (XmlUtils.nextElementWithin(parser,outerDepth)) { 615 if (TAG_PERM.equals(parser.getName())) { 616 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME); 617 outRequestedPermissions.add(permission); 618 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) { 619 outGrantedPermissions.add(permission); 620 } 621 } 622 } 623 } 624 625 private void writeUninstalledEphemeralAppMetadata( 626 EphemeralApplicationInfo ephemeralApp, int userId) { 627 File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId); 628 if (!appDir.exists() && !appDir.mkdirs()) { 629 return; 630 } 631 632 File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE); 633 634 AtomicFile destination = new AtomicFile(metadataFile); 635 FileOutputStream out = null; 636 try { 637 out = destination.startWrite(); 638 639 XmlSerializer serializer = Xml.newSerializer(); 640 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 641 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 642 643 serializer.startDocument(null, true); 644 645 serializer.startTag(null, TAG_PACKAGE); 646 serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel( 647 mService.mContext.getPackageManager()).toString()); 648 649 serializer.startTag(null, TAG_PERMS); 650 for (String permission : ephemeralApp.getRequestedPermissions()) { 651 serializer.startTag(null, TAG_PERM); 652 serializer.attribute(null, ATTR_NAME, permission); 653 if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) { 654 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true)); 655 } 656 serializer.endTag(null, TAG_PERM); 657 } 658 serializer.endTag(null, TAG_PERMS); 659 660 serializer.endTag(null, TAG_PACKAGE); 661 662 serializer.endDocument(); 663 destination.finishWrite(out); 664 } catch (Throwable t) { 665 Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t); 666 destination.failWrite(out); 667 } finally { 668 IoUtils.closeQuietly(out); 669 } 670 } 671 672 private static String computePackageCertDigest(PackageParser.Package pkg) { 673 MessageDigest messageDigest; 674 try { 675 messageDigest = MessageDigest.getInstance("SHA256"); 676 } catch (NoSuchAlgorithmException e) { 677 /* can't happen */ 678 return null; 679 } 680 681 messageDigest.update(pkg.mSignatures[0].toByteArray()); 682 683 final byte[] digest = messageDigest.digest(); 684 final int digestLength = digest.length; 685 final int charCount = 2 * digestLength; 686 687 final char[] chars = new char[charCount]; 688 for (int i = 0; i < digestLength; i++) { 689 final int byteHex = digest[i] & 0xFF; 690 chars[i * 2] = HEX_ARRAY[byteHex >>> 4]; 691 chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F]; 692 } 693 return new String(chars); 694 } 695 696 private static File getEphemeralApplicationsDir(int userId) { 697 return new File(Environment.getUserSystemDirectory(userId), 698 EPHEMERAL_APPS_FOLDER); 699 } 700 701 private static File getEphemeralApplicationDir(String packageName, int userId) { 702 return new File (getEphemeralApplicationsDir(userId), packageName); 703 } 704 705 private static void deleteDir(File dir) { 706 File[] files = dir.listFiles(); 707 if (files != null) { 708 for (File file : dir.listFiles()) { 709 deleteDir(file); 710 } 711 } 712 dir.delete(); 713 } 714 715 private static final class UninstalledEphemeralAppState { 716 final EphemeralApplicationInfo mEphemeralApplicationInfo; 717 final long mTimestamp; 718 719 public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp, 720 long timestamp) { 721 mEphemeralApplicationInfo = ephemeralApp; 722 mTimestamp = timestamp; 723 } 724 } 725 } 726