1 /* 2 * Copyright 2020 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 package com.android.server.blob; 17 18 import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS; 19 import static android.app.blob.XmlTags.ATTR_DESCRIPTION; 20 import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME; 21 import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; 22 import static android.app.blob.XmlTags.ATTR_ID; 23 import static android.app.blob.XmlTags.ATTR_PACKAGE; 24 import static android.app.blob.XmlTags.ATTR_UID; 25 import static android.app.blob.XmlTags.ATTR_USER_ID; 26 import static android.app.blob.XmlTags.TAG_ACCESS_MODE; 27 import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; 28 import static android.app.blob.XmlTags.TAG_COMMITTER; 29 import static android.app.blob.XmlTags.TAG_LEASEE; 30 import static android.os.Process.INVALID_UID; 31 import static android.system.OsConstants.O_RDONLY; 32 import static android.text.format.Formatter.FLAG_IEC_UNITS; 33 import static android.text.format.Formatter.formatFileSize; 34 35 import static com.android.server.blob.BlobStoreConfig.TAG; 36 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; 37 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME; 38 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; 39 import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed; 40 import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; 41 import static com.android.server.blob.BlobStoreUtils.getPackageResources; 42 43 import android.annotation.NonNull; 44 import android.annotation.Nullable; 45 import android.app.blob.BlobHandle; 46 import android.app.blob.LeaseInfo; 47 import android.content.Context; 48 import android.content.res.ResourceId; 49 import android.content.res.Resources; 50 import android.os.ParcelFileDescriptor; 51 import android.os.RevocableFileDescriptor; 52 import android.os.UserHandle; 53 import android.system.ErrnoException; 54 import android.system.Os; 55 import android.util.ArrayMap; 56 import android.util.ArraySet; 57 import android.util.Slog; 58 import android.util.SparseArray; 59 import android.util.StatsEvent; 60 import android.util.proto.ProtoOutputStream; 61 62 import com.android.internal.annotations.GuardedBy; 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.internal.util.IndentingPrintWriter; 65 import com.android.internal.util.XmlUtils; 66 import com.android.server.blob.BlobStoreManagerService.DumpArgs; 67 68 import libcore.io.IoUtils; 69 70 import org.xmlpull.v1.XmlPullParser; 71 import org.xmlpull.v1.XmlPullParserException; 72 import org.xmlpull.v1.XmlSerializer; 73 74 import java.io.File; 75 import java.io.FileDescriptor; 76 import java.io.IOException; 77 import java.util.Objects; 78 import java.util.function.Consumer; 79 80 class BlobMetadata { 81 private final Object mMetadataLock = new Object(); 82 83 private final Context mContext; 84 85 private final long mBlobId; 86 private final BlobHandle mBlobHandle; 87 private final int mUserId; 88 89 @GuardedBy("mMetadataLock") 90 private final ArraySet<Committer> mCommitters = new ArraySet<>(); 91 92 @GuardedBy("mMetadataLock") 93 private final ArraySet<Leasee> mLeasees = new ArraySet<>(); 94 95 /** 96 * Contains packageName -> {RevocableFileDescriptors}. 97 * 98 * Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so 99 * that when clients access is revoked or the blob gets deleted, we can be sure that clients 100 * do not have any reference to the blob and the space occupied by the blob can be freed. 101 */ 102 @GuardedBy("mRevocableFds") 103 private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds = 104 new ArrayMap<>(); 105 106 // Do not access this directly, instead use #getBlobFile(). 107 private File mBlobFile; 108 BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId)109 BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) { 110 mContext = context; 111 this.mBlobId = blobId; 112 this.mBlobHandle = blobHandle; 113 this.mUserId = userId; 114 } 115 getBlobId()116 long getBlobId() { 117 return mBlobId; 118 } 119 getBlobHandle()120 BlobHandle getBlobHandle() { 121 return mBlobHandle; 122 } 123 getUserId()124 int getUserId() { 125 return mUserId; 126 } 127 addOrReplaceCommitter(@onNull Committer committer)128 void addOrReplaceCommitter(@NonNull Committer committer) { 129 synchronized (mMetadataLock) { 130 // We need to override the committer data, so first remove any existing 131 // committer before adding the new one. 132 mCommitters.remove(committer); 133 mCommitters.add(committer); 134 } 135 } 136 setCommitters(ArraySet<Committer> committers)137 void setCommitters(ArraySet<Committer> committers) { 138 synchronized (mMetadataLock) { 139 mCommitters.clear(); 140 mCommitters.addAll(committers); 141 } 142 } 143 removeCommitter(@onNull String packageName, int uid)144 void removeCommitter(@NonNull String packageName, int uid) { 145 synchronized (mMetadataLock) { 146 mCommitters.removeIf((committer) -> 147 committer.uid == uid && committer.packageName.equals(packageName)); 148 } 149 } 150 removeCommitter(@onNull Committer committer)151 void removeCommitter(@NonNull Committer committer) { 152 synchronized (mMetadataLock) { 153 mCommitters.remove(committer); 154 } 155 } 156 removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages)157 void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) { 158 synchronized (mMetadataLock) { 159 mCommitters.removeIf(committer -> 160 !committer.packageName.equals(knownPackages.get(committer.uid))); 161 } 162 } 163 164 @Nullable getExistingCommitter(@onNull String packageName, int uid)165 Committer getExistingCommitter(@NonNull String packageName, int uid) { 166 synchronized (mCommitters) { 167 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 168 final Committer committer = mCommitters.valueAt(i); 169 if (committer.uid == uid && committer.packageName.equals(packageName)) { 170 return committer; 171 } 172 } 173 } 174 return null; 175 } 176 addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis)177 void addOrReplaceLeasee(String callingPackage, int callingUid, int descriptionResId, 178 CharSequence description, long leaseExpiryTimeMillis) { 179 synchronized (mMetadataLock) { 180 // We need to override the leasee data, so first remove any existing 181 // leasee before adding the new one. 182 final Leasee leasee = new Leasee(mContext, callingPackage, callingUid, 183 descriptionResId, description, leaseExpiryTimeMillis); 184 mLeasees.remove(leasee); 185 mLeasees.add(leasee); 186 } 187 } 188 setLeasees(ArraySet<Leasee> leasees)189 void setLeasees(ArraySet<Leasee> leasees) { 190 synchronized (mMetadataLock) { 191 mLeasees.clear(); 192 mLeasees.addAll(leasees); 193 } 194 } 195 removeLeasee(String packageName, int uid)196 void removeLeasee(String packageName, int uid) { 197 synchronized (mMetadataLock) { 198 mLeasees.removeIf((leasee) -> 199 leasee.uid == uid && leasee.packageName.equals(packageName)); 200 } 201 } 202 removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages)203 void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) { 204 synchronized (mMetadataLock) { 205 mLeasees.removeIf(leasee -> 206 !leasee.packageName.equals(knownPackages.get(leasee.uid))); 207 } 208 } 209 removeExpiredLeases()210 void removeExpiredLeases() { 211 synchronized (mMetadataLock) { 212 mLeasees.removeIf(leasee -> !leasee.isStillValid()); 213 } 214 } 215 hasValidLeases()216 boolean hasValidLeases() { 217 synchronized (mMetadataLock) { 218 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 219 if (mLeasees.valueAt(i).isStillValid()) { 220 return true; 221 } 222 } 223 return false; 224 } 225 } 226 getSize()227 long getSize() { 228 return getBlobFile().length(); 229 } 230 isAccessAllowedForCaller(@onNull String callingPackage, int callingUid)231 boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid) { 232 // Don't allow the blob to be accessed after it's expiry time has passed. 233 if (getBlobHandle().isExpired()) { 234 return false; 235 } 236 synchronized (mMetadataLock) { 237 // Check if packageName already holds a lease on the blob. 238 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 239 final Leasee leasee = mLeasees.valueAt(i); 240 if (leasee.isStillValid() && leasee.equals(callingPackage, callingUid)) { 241 return true; 242 } 243 } 244 245 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 246 final Committer committer = mCommitters.valueAt(i); 247 248 // Check if the caller is the same package that committed the blob. 249 if (committer.equals(callingPackage, callingUid)) { 250 return true; 251 } 252 253 // Check if the caller is allowed access as per the access mode specified 254 // by the committer. 255 if (committer.blobAccessMode.isAccessAllowedForCaller(mContext, 256 callingPackage, committer.packageName)) { 257 return true; 258 } 259 } 260 } 261 return false; 262 } 263 isACommitter(@onNull String packageName, int uid)264 boolean isACommitter(@NonNull String packageName, int uid) { 265 synchronized (mMetadataLock) { 266 return isAnAccessor(mCommitters, packageName, uid); 267 } 268 } 269 isALeasee(@ullable String packageName, int uid)270 boolean isALeasee(@Nullable String packageName, int uid) { 271 synchronized (mMetadataLock) { 272 final Leasee leasee = getAccessor(mLeasees, packageName, uid); 273 return leasee != null && leasee.isStillValid(); 274 } 275 } 276 isAnAccessor(@onNull ArraySet<T> accessors, @Nullable String packageName, int uid)277 private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors, 278 @Nullable String packageName, int uid) { 279 // Check if the package is an accessor of the data blob. 280 return getAccessor(accessors, packageName, uid) != null; 281 } 282 getAccessor(@onNull ArraySet<T> accessors, @Nullable String packageName, int uid)283 private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors, 284 @Nullable String packageName, int uid) { 285 // Check if the package is an accessor of the data blob. 286 for (int i = 0, size = accessors.size(); i < size; ++i) { 287 final Accessor accessor = accessors.valueAt(i); 288 if (packageName != null && uid != INVALID_UID 289 && accessor.equals(packageName, uid)) { 290 return (T) accessor; 291 } else if (packageName != null && accessor.packageName.equals(packageName)) { 292 return (T) accessor; 293 } else if (uid != INVALID_UID && accessor.uid == uid) { 294 return (T) accessor; 295 } 296 } 297 return null; 298 } 299 isALeasee(@onNull String packageName)300 boolean isALeasee(@NonNull String packageName) { 301 return isALeasee(packageName, INVALID_UID); 302 } 303 isALeasee(int uid)304 boolean isALeasee(int uid) { 305 return isALeasee(null, uid); 306 } 307 hasOtherLeasees(@onNull String packageName)308 boolean hasOtherLeasees(@NonNull String packageName) { 309 return hasOtherLeasees(packageName, INVALID_UID); 310 } 311 hasOtherLeasees(int uid)312 boolean hasOtherLeasees(int uid) { 313 return hasOtherLeasees(null, uid); 314 } 315 hasOtherLeasees(@ullable String packageName, int uid)316 private boolean hasOtherLeasees(@Nullable String packageName, int uid) { 317 synchronized (mMetadataLock) { 318 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 319 final Leasee leasee = mLeasees.valueAt(i); 320 if (!leasee.isStillValid()) { 321 continue; 322 } 323 // TODO: Also exclude packages which are signed with same cert? 324 if (packageName != null && uid != INVALID_UID 325 && !leasee.equals(packageName, uid)) { 326 return true; 327 } else if (packageName != null && !leasee.packageName.equals(packageName)) { 328 return true; 329 } else if (uid != INVALID_UID && leasee.uid != uid) { 330 return true; 331 } 332 } 333 } 334 return false; 335 } 336 337 @Nullable getLeaseInfo(@onNull String packageName, int uid)338 LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) { 339 synchronized (mMetadataLock) { 340 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 341 final Leasee leasee = mLeasees.valueAt(i); 342 if (!leasee.isStillValid()) { 343 continue; 344 } 345 if (leasee.uid == uid && leasee.packageName.equals(packageName)) { 346 final int descriptionResId = leasee.descriptionResEntryName == null 347 ? Resources.ID_NULL 348 : BlobStoreUtils.getDescriptionResourceId( 349 mContext, leasee.descriptionResEntryName, leasee.packageName, 350 UserHandle.getUserId(leasee.uid)); 351 return new LeaseInfo(packageName, leasee.expiryTimeMillis, 352 descriptionResId, leasee.description); 353 } 354 } 355 } 356 return null; 357 } 358 forEachLeasee(Consumer<Leasee> consumer)359 void forEachLeasee(Consumer<Leasee> consumer) { 360 synchronized (mMetadataLock) { 361 mLeasees.forEach(consumer); 362 } 363 } 364 getBlobFile()365 File getBlobFile() { 366 if (mBlobFile == null) { 367 mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); 368 } 369 return mBlobFile; 370 } 371 openForRead(String callingPackage)372 ParcelFileDescriptor openForRead(String callingPackage) throws IOException { 373 // TODO: Add limit on opened fds 374 FileDescriptor fd; 375 try { 376 fd = Os.open(getBlobFile().getPath(), O_RDONLY, 0); 377 } catch (ErrnoException e) { 378 throw e.rethrowAsIOException(); 379 } 380 try { 381 if (BlobStoreConfig.shouldUseRevocableFdForReads()) { 382 return createRevocableFd(fd, callingPackage); 383 } else { 384 return new ParcelFileDescriptor(fd); 385 } 386 } catch (IOException e) { 387 IoUtils.closeQuietly(fd); 388 throw e; 389 } 390 } 391 392 @NonNull createRevocableFd(FileDescriptor fd, String callingPackage)393 private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, 394 String callingPackage) throws IOException { 395 final RevocableFileDescriptor revocableFd = 396 new RevocableFileDescriptor(mContext, fd); 397 synchronized (mRevocableFds) { 398 ArraySet<RevocableFileDescriptor> revocableFdsForPkg = 399 mRevocableFds.get(callingPackage); 400 if (revocableFdsForPkg == null) { 401 revocableFdsForPkg = new ArraySet<>(); 402 mRevocableFds.put(callingPackage, revocableFdsForPkg); 403 } 404 revocableFdsForPkg.add(revocableFd); 405 } 406 revocableFd.addOnCloseListener((e) -> { 407 synchronized (mRevocableFds) { 408 final ArraySet<RevocableFileDescriptor> revocableFdsForPkg = 409 mRevocableFds.get(callingPackage); 410 if (revocableFdsForPkg != null) { 411 revocableFdsForPkg.remove(revocableFd); 412 if (revocableFdsForPkg.isEmpty()) { 413 mRevocableFds.remove(callingPackage); 414 } 415 } 416 } 417 }); 418 return revocableFd.getRevocableFileDescriptor(); 419 } 420 destroy()421 void destroy() { 422 revokeAllFds(); 423 getBlobFile().delete(); 424 } 425 revokeAllFds()426 private void revokeAllFds() { 427 synchronized (mRevocableFds) { 428 for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) { 429 final ArraySet<RevocableFileDescriptor> packageFds = 430 mRevocableFds.valueAt(i); 431 if (packageFds == null) { 432 continue; 433 } 434 for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) { 435 packageFds.valueAt(j).revoke(); 436 } 437 } 438 } 439 } 440 shouldBeDeleted(boolean respectLeaseWaitTime)441 boolean shouldBeDeleted(boolean respectLeaseWaitTime) { 442 // Expired data blobs 443 if (getBlobHandle().isExpired()) { 444 return true; 445 } 446 447 // Blobs with no active leases 448 if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll()) 449 && !hasValidLeases()) { 450 return true; 451 } 452 453 return false; 454 } 455 456 @VisibleForTesting hasLeaseWaitTimeElapsedForAll()457 boolean hasLeaseWaitTimeElapsedForAll() { 458 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 459 final Committer committer = mCommitters.valueAt(i); 460 if (!hasLeaseWaitTimeElapsed(committer.getCommitTimeMs())) { 461 return false; 462 } 463 } 464 return true; 465 } 466 dumpAsStatsEvent(int atomTag)467 StatsEvent dumpAsStatsEvent(int atomTag) { 468 synchronized (mMetadataLock) { 469 ProtoOutputStream proto = new ProtoOutputStream(); 470 // Write Committer data to proto format 471 for (int i = 0, size = mCommitters.size(); i < size; ++i) { 472 final Committer committer = mCommitters.valueAt(i); 473 final long token = proto.start( 474 BlobStatsEventProto.BlobCommitterListProto.COMMITTER); 475 proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid); 476 proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS, 477 committer.commitTimeMs); 478 proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE, 479 committer.blobAccessMode.getAccessType()); 480 proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE, 481 committer.blobAccessMode.getNumWhitelistedPackages()); 482 proto.end(token); 483 } 484 final byte[] committersBytes = proto.getBytes(); 485 486 proto = new ProtoOutputStream(); 487 // Write Leasee data to proto format 488 for (int i = 0, size = mLeasees.size(); i < size; ++i) { 489 final Leasee leasee = mLeasees.valueAt(i); 490 final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE); 491 proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid); 492 proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS, 493 leasee.expiryTimeMillis); 494 proto.end(token); 495 } 496 final byte[] leaseesBytes = proto.getBytes(); 497 498 // Construct the StatsEvent to represent this Blob 499 return StatsEvent.newBuilder() 500 .setAtomId(atomTag) 501 .writeLong(mBlobId) 502 .writeLong(getSize()) 503 .writeLong(mBlobHandle.getExpiryTimeMillis()) 504 .writeByteArray(committersBytes) 505 .writeByteArray(leaseesBytes) 506 .build(); 507 } 508 } 509 dump(IndentingPrintWriter fout, DumpArgs dumpArgs)510 void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { 511 synchronized (mMetadataLock) { 512 fout.println("blobHandle:"); 513 fout.increaseIndent(); 514 mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); 515 fout.decreaseIndent(); 516 fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); 517 518 fout.println("Committers:"); 519 fout.increaseIndent(); 520 if (mCommitters.isEmpty()) { 521 fout.println("<empty>"); 522 } else { 523 for (int i = 0, count = mCommitters.size(); i < count; ++i) { 524 final Committer committer = mCommitters.valueAt(i); 525 fout.println("committer " + committer.toString()); 526 fout.increaseIndent(); 527 committer.dump(fout); 528 fout.decreaseIndent(); 529 } 530 } 531 fout.decreaseIndent(); 532 533 fout.println("Leasees:"); 534 fout.increaseIndent(); 535 if (mLeasees.isEmpty()) { 536 fout.println("<empty>"); 537 } else { 538 for (int i = 0, count = mLeasees.size(); i < count; ++i) { 539 final Leasee leasee = mLeasees.valueAt(i); 540 fout.println("leasee " + leasee.toString()); 541 fout.increaseIndent(); 542 leasee.dump(mContext, fout); 543 fout.decreaseIndent(); 544 } 545 } 546 fout.decreaseIndent(); 547 548 fout.println("Open fds:"); 549 fout.increaseIndent(); 550 if (mRevocableFds.isEmpty()) { 551 fout.println("<empty>"); 552 } else { 553 for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { 554 final String packageName = mRevocableFds.keyAt(i); 555 final ArraySet<RevocableFileDescriptor> packageFds = 556 mRevocableFds.valueAt(i); 557 fout.println(packageName + "#" + packageFds.size()); 558 } 559 } 560 fout.decreaseIndent(); 561 } 562 } 563 writeToXml(XmlSerializer out)564 void writeToXml(XmlSerializer out) throws IOException { 565 synchronized (mMetadataLock) { 566 XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); 567 XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId); 568 569 out.startTag(null, TAG_BLOB_HANDLE); 570 mBlobHandle.writeToXml(out); 571 out.endTag(null, TAG_BLOB_HANDLE); 572 573 for (int i = 0, count = mCommitters.size(); i < count; ++i) { 574 out.startTag(null, TAG_COMMITTER); 575 mCommitters.valueAt(i).writeToXml(out); 576 out.endTag(null, TAG_COMMITTER); 577 } 578 579 for (int i = 0, count = mLeasees.size(); i < count; ++i) { 580 out.startTag(null, TAG_LEASEE); 581 mLeasees.valueAt(i).writeToXml(out); 582 out.endTag(null, TAG_LEASEE); 583 } 584 } 585 } 586 587 @Nullable createFromXml(XmlPullParser in, int version, Context context)588 static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) 589 throws XmlPullParserException, IOException { 590 final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); 591 final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID); 592 593 BlobHandle blobHandle = null; 594 final ArraySet<Committer> committers = new ArraySet<>(); 595 final ArraySet<Leasee> leasees = new ArraySet<>(); 596 final int depth = in.getDepth(); 597 while (XmlUtils.nextElementWithin(in, depth)) { 598 if (TAG_BLOB_HANDLE.equals(in.getName())) { 599 blobHandle = BlobHandle.createFromXml(in); 600 } else if (TAG_COMMITTER.equals(in.getName())) { 601 final Committer committer = Committer.createFromXml(in, version); 602 if (committer != null) { 603 committers.add(committer); 604 } 605 } else if (TAG_LEASEE.equals(in.getName())) { 606 leasees.add(Leasee.createFromXml(in, version)); 607 } 608 } 609 610 if (blobHandle == null) { 611 Slog.wtf(TAG, "blobHandle should be available"); 612 return null; 613 } 614 615 final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId); 616 blobMetadata.setCommitters(committers); 617 blobMetadata.setLeasees(leasees); 618 return blobMetadata; 619 } 620 621 static final class Committer extends Accessor { 622 public final BlobAccessMode blobAccessMode; 623 public final long commitTimeMs; 624 Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs)625 Committer(String packageName, int uid, BlobAccessMode blobAccessMode, long commitTimeMs) { 626 super(packageName, uid); 627 this.blobAccessMode = blobAccessMode; 628 this.commitTimeMs = commitTimeMs; 629 } 630 getCommitTimeMs()631 long getCommitTimeMs() { 632 return commitTimeMs; 633 } 634 dump(IndentingPrintWriter fout)635 void dump(IndentingPrintWriter fout) { 636 fout.println("commit time: " 637 + (commitTimeMs == 0 ? "<null>" : BlobStoreUtils.formatTime(commitTimeMs))); 638 fout.println("accessMode:"); 639 fout.increaseIndent(); 640 blobAccessMode.dump(fout); 641 fout.decreaseIndent(); 642 } 643 writeToXml(@onNull XmlSerializer out)644 void writeToXml(@NonNull XmlSerializer out) throws IOException { 645 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); 646 XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 647 XmlUtils.writeLongAttribute(out, ATTR_COMMIT_TIME_MS, commitTimeMs); 648 649 out.startTag(null, TAG_ACCESS_MODE); 650 blobAccessMode.writeToXml(out); 651 out.endTag(null, TAG_ACCESS_MODE); 652 } 653 654 @Nullable createFromXml(@onNull XmlPullParser in, int version)655 static Committer createFromXml(@NonNull XmlPullParser in, int version) 656 throws XmlPullParserException, IOException { 657 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 658 final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); 659 final long commitTimeMs = version >= XML_VERSION_ADD_COMMIT_TIME 660 ? XmlUtils.readLongAttribute(in, ATTR_COMMIT_TIME_MS) 661 : 0; 662 663 final int depth = in.getDepth(); 664 BlobAccessMode blobAccessMode = null; 665 while (XmlUtils.nextElementWithin(in, depth)) { 666 if (TAG_ACCESS_MODE.equals(in.getName())) { 667 blobAccessMode = BlobAccessMode.createFromXml(in); 668 } 669 } 670 if (blobAccessMode == null) { 671 Slog.wtf(TAG, "blobAccessMode should be available"); 672 return null; 673 } 674 return new Committer(packageName, uid, blobAccessMode, commitTimeMs); 675 } 676 } 677 678 static final class Leasee extends Accessor { 679 public final String descriptionResEntryName; 680 public final CharSequence description; 681 public final long expiryTimeMillis; 682 Leasee(@onNull Context context, @NonNull String packageName, int uid, int descriptionResId, @Nullable CharSequence description, long expiryTimeMillis)683 Leasee(@NonNull Context context, @NonNull String packageName, 684 int uid, int descriptionResId, 685 @Nullable CharSequence description, long expiryTimeMillis) { 686 super(packageName, uid); 687 final Resources packageResources = getPackageResources(context, packageName, 688 UserHandle.getUserId(uid)); 689 this.descriptionResEntryName = getResourceEntryName(packageResources, descriptionResId); 690 this.expiryTimeMillis = expiryTimeMillis; 691 this.description = description == null 692 ? getDescription(packageResources, descriptionResId) 693 : description; 694 } 695 Leasee(String packageName, int uid, @Nullable String descriptionResEntryName, @Nullable CharSequence description, long expiryTimeMillis)696 Leasee(String packageName, int uid, @Nullable String descriptionResEntryName, 697 @Nullable CharSequence description, long expiryTimeMillis) { 698 super(packageName, uid); 699 this.descriptionResEntryName = descriptionResEntryName; 700 this.expiryTimeMillis = expiryTimeMillis; 701 this.description = description; 702 } 703 704 @Nullable getResourceEntryName(@ullable Resources packageResources, int resId)705 private static String getResourceEntryName(@Nullable Resources packageResources, 706 int resId) { 707 if (!ResourceId.isValid(resId) || packageResources == null) { 708 return null; 709 } 710 return packageResources.getResourceEntryName(resId); 711 } 712 713 @Nullable getDescription(@onNull Context context, @NonNull String descriptionResEntryName, @NonNull String packageName, int userId)714 private static String getDescription(@NonNull Context context, 715 @NonNull String descriptionResEntryName, @NonNull String packageName, int userId) { 716 if (descriptionResEntryName == null || descriptionResEntryName.isEmpty()) { 717 return null; 718 } 719 final Resources resources = getPackageResources(context, packageName, userId); 720 if (resources == null) { 721 return null; 722 } 723 final int resId = getDescriptionResourceId(resources, descriptionResEntryName, 724 packageName); 725 return resId == Resources.ID_NULL ? null : resources.getString(resId); 726 } 727 728 @Nullable getDescription(@ullable Resources packageResources, int descriptionResId)729 private static String getDescription(@Nullable Resources packageResources, 730 int descriptionResId) { 731 if (!ResourceId.isValid(descriptionResId) || packageResources == null) { 732 return null; 733 } 734 return packageResources.getString(descriptionResId); 735 } 736 isStillValid()737 boolean isStillValid() { 738 return expiryTimeMillis == 0 || expiryTimeMillis >= System.currentTimeMillis(); 739 } 740 dump(@onNull Context context, @NonNull IndentingPrintWriter fout)741 void dump(@NonNull Context context, @NonNull IndentingPrintWriter fout) { 742 fout.println("desc: " + getDescriptionToDump(context)); 743 fout.println("expiryMs: " + expiryTimeMillis); 744 } 745 746 @NonNull getDescriptionToDump(@onNull Context context)747 private String getDescriptionToDump(@NonNull Context context) { 748 String desc = getDescription(context, descriptionResEntryName, packageName, 749 UserHandle.getUserId(uid)); 750 if (desc == null) { 751 desc = description.toString(); 752 } 753 return desc == null ? "<none>" : desc; 754 } 755 writeToXml(@onNull XmlSerializer out)756 void writeToXml(@NonNull XmlSerializer out) throws IOException { 757 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); 758 XmlUtils.writeIntAttribute(out, ATTR_UID, uid); 759 XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION_RES_NAME, descriptionResEntryName); 760 XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); 761 XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description); 762 } 763 764 @NonNull createFromXml(@onNull XmlPullParser in, int version)765 static Leasee createFromXml(@NonNull XmlPullParser in, int version) 766 throws IOException { 767 final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 768 final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); 769 final String descriptionResEntryName; 770 if (version >= XML_VERSION_ADD_DESC_RES_NAME) { 771 descriptionResEntryName = XmlUtils.readStringAttribute( 772 in, ATTR_DESCRIPTION_RES_NAME); 773 } else { 774 descriptionResEntryName = null; 775 } 776 final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); 777 final CharSequence description; 778 if (version >= XML_VERSION_ADD_STRING_DESC) { 779 description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION); 780 } else { 781 description = null; 782 } 783 784 return new Leasee(packageName, uid, descriptionResEntryName, 785 description, expiryTimeMillis); 786 } 787 } 788 789 static class Accessor { 790 public final String packageName; 791 public final int uid; 792 Accessor(String packageName, int uid)793 Accessor(String packageName, int uid) { 794 this.packageName = packageName; 795 this.uid = uid; 796 } 797 equals(String packageName, int uid)798 public boolean equals(String packageName, int uid) { 799 return this.uid == uid && this.packageName.equals(packageName); 800 } 801 802 @Override equals(Object obj)803 public boolean equals(Object obj) { 804 if (this == obj) { 805 return true; 806 } 807 if (obj == null || !(obj instanceof Accessor)) { 808 return false; 809 } 810 final Accessor other = (Accessor) obj; 811 return this.uid == other.uid && this.packageName.equals(other.packageName); 812 } 813 814 @Override hashCode()815 public int hashCode() { 816 return Objects.hash(packageName, uid); 817 } 818 819 @Override toString()820 public String toString() { 821 return "[" + packageName + ", " + uid + "]"; 822 } 823 } 824 } 825