Don't allow expired blobs to be accessed.

Bug: 147722548
Test: atest --test-mapping apex/blobstore
Change-Id: I7294124ba5ce3dcf42a0e7e9858311b1604ae185
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 49b3ec1..cea7fcc 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -212,7 +212,10 @@
     }
 
     boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid) {
-        // TODO: verify blob is still valid (expiryTime is not elapsed)
+        // Don't allow the blob to be accessed after it's expiry time has passed.
+        if (getBlobHandle().isExpired()) {
+            return false;
+        }
         synchronized (mMetadataLock) {
             // Check if packageName already holds a lease on the blob.
             for (int i = 0, size = mLeasees.size(); i < size; ++i) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 7a27b2c..a2bce31 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1059,6 +1059,18 @@
         }
     }
 
+    boolean isBlobAvailable(long blobId, int userId) {
+        synchronized (mBlobsLock) {
+            final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
+            for (BlobMetadata blobMetadata : userBlobs.values()) {
+                if (blobMetadata.getBlobId() == blobId) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     @GuardedBy("mBlobsLock")
     private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
         for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index 72af323..a4a2e80 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -46,6 +46,8 @@
                 return runDeleteBlob(pw);
             case "idle-maintenance":
                 return runIdleMaintenance(pw);
+            case "query-blob-existence":
+                return runQueryBlobExistence(pw);
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -91,6 +93,16 @@
         return 0;
     }
 
+    private int runQueryBlobExistence(PrintWriter pw) {
+        final ParsedArgs args = new ParsedArgs();
+        if (parseOptions(pw, args) < 0) {
+            return -1;
+        }
+
+        pw.println(mService.isBlobAvailable(args.blobId, args.userId) ? 1 : 0);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -121,6 +133,8 @@
         pw.println("      --tag: Tag of the blob to delete.");
         pw.println("idle-maintenance");
         pw.println("    Run idle maintenance which takes care of removing stale data.");
+        pw.println("query-blob-existence [-b BLOB_ID]");
+        pw.println("    Prints 1 if blob exists, otherwise 0.");
         pw.println();
     }
 
@@ -147,6 +161,9 @@
                 case "--tag":
                     args.tag = getNextArgRequired();
                     break;
+                case "-b":
+                    args.blobId = Long.parseLong(getNextArgRequired());
+                    break;
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -166,6 +183,7 @@
         public long expiryTimeMillis;
         public CharSequence label;
         public String tag;
+        public long blobId;
 
         public BlobHandle getBlobHandle() {
             return BlobHandle.create(algorithm, digest, label, expiryTimeMillis, tag);
diff --git a/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp
index 5c7c68b..53d3638 100644
--- a/tests/BlobStoreTestUtils/Android.bp
+++ b/tests/BlobStoreTestUtils/Android.bp
@@ -18,6 +18,7 @@
   static_libs: [
     "truth-prebuilt",
     "androidx.test.uiautomator_uiautomator",
+    "androidx.test.ext.junit",
   ],
   sdk_version: "test_current",
 }
\ No newline at end of file
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
index 371375c..4a0ca66 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
@@ -42,6 +42,7 @@
     private final File mFile;
     private final long mFileSize;
     private final CharSequence mLabel;
+    private final long mExpiryDurationMs;
 
     byte[] mFileDigest;
     long mExpiryTimeMs;
@@ -51,6 +52,7 @@
         mFile = new File(builder.getContext().getFilesDir(), builder.getFileName());
         mFileSize = builder.getFileSize();
         mLabel = builder.getLabel();
+        mExpiryDurationMs = builder.getExpiryDurationMs();
     }
 
     public static class Builder {
@@ -59,6 +61,7 @@
         private long mFileSize = DEFAULT_SIZE_BYTES;
         private CharSequence mLabel = "Test label";
         private String mFileName = "blob_" + System.nanoTime();
+        private long mExpiryDurationMs = TimeUnit.DAYS.toMillis(1);
 
         public Builder(Context context) {
             mContext = context;
@@ -104,6 +107,15 @@
             return mFileName;
         }
 
+        public Builder setExpiryDurationMs(long durationMs) {
+            mExpiryDurationMs = durationMs;
+            return this;
+        }
+
+        public long getExpiryDurationMs() {
+            return mExpiryDurationMs;
+        }
+
         public DummyBlobData build() {
             return new DummyBlobData(this);
         }
@@ -114,7 +126,7 @@
             writeRandomData(file, mFileSize);
         }
         mFileDigest = FileUtils.digest(mFile, "SHA-256");
-        mExpiryTimeMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
+        mExpiryTimeMs = System.currentTimeMillis() + mExpiryDurationMs;
     }
 
     public BlobHandle getBlobHandle() throws Exception {
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index 7cf58e1..b9bd661 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.Instrumentation;
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobStoreManager;
 import android.app.blob.LeaseInfo;
@@ -27,6 +26,7 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
 import java.io.FileInputStream;
@@ -149,14 +149,14 @@
         assertThat(leaseInfo.getDescription()).isEqualTo(description);
     }
 
-    public static void triggerIdleMaintenance(Instrumentation instrumentation) throws IOException {
-        runShellCmd(instrumentation, "cmd blob_store idle-maintenance");
+    public static void triggerIdleMaintenance() throws IOException {
+        runShellCmd("cmd blob_store idle-maintenance");
     }
 
-    private static String runShellCmd(Instrumentation instrumentation,
-            String cmd) throws IOException {
-        final UiDevice uiDevice = UiDevice.getInstance(instrumentation);
-        final String result = uiDevice.executeShellCommand(cmd);
+    public static String runShellCmd(String cmd) throws IOException {
+        final UiDevice uiDevice = UiDevice.getInstance(
+                InstrumentationRegistry.getInstrumentation());
+        final String result = uiDevice.executeShellCommand(cmd).trim();
         Log.i(TAG, "Output of '" + cmd + "': '" + result + "'");
         return result;
     }