Use Files uri in Upsert

When Fuse inserts a file to database it does not set is_download
column. When app tries insert with Download uri, upsert fails because
getIdIfPathExistsForCallingPackage can't find a row ID with
is_download=1.

Use Files uri to ensure upsert updates existing row.

Test: atest MediaStore_Images_MediaTest#testUpsert
Bug: 149300533
Change-Id: I25435f54828dfc63df496ce59130f79d784f7193
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 37d1e17..3089c19 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -2745,12 +2745,13 @@
             try {
                 return qb.insert(helper, values);
             } catch (SQLiteConstraintException e) {
-                final long rowId = getIdIfPathExistsForCallingPackage(qb, helper, path);
+                SQLiteQueryBuilder qbForUpsert = getQueryBuilderForUpsert(path);
+                final long rowId = getIdIfPathExistsForCallingPackage(qbForUpsert, helper, path);
                 // Apps sometimes create a file via direct path and then insert it into
                 // MediaStore via ContentResolver. The former should create a database entry,
                 // so we have to treat the latter as an upsert.
                 // TODO(b/149917493) Perform all INSERT operations as UPSERT.
-                if (rowId != -1 && qb.update(helper, values, "_id=?",
+                if (rowId != -1 && qbForUpsert.update(helper, values, "_id=?",
                         new String[]{Long.toString(rowId)}) == 1) {
                     return rowId;
                 }
@@ -2781,6 +2782,28 @@
         return -1;
     }
 
+    /**
+     * @return {@link SQLiteQueryBuilder} for upsert with Files uri. This disables strict columns
+     * check to allow upsert to update any column with Files uri.
+     */
+    private SQLiteQueryBuilder getQueryBuilderForUpsert(@NonNull String path) {
+        final Uri uri = Files.getContentUriForPath(path);
+        final boolean allowHidden = isCallingPackageAllowedHidden();
+        // When Fuse inserts a file to database it doesn't set is_download column. When app tries
+        // insert with Downloads uri, upsert fails because getIdIfPathExistsForCallingPackage can't
+        // find a row ID with is_download=1. Use Files uri to query & update any existing row
+        // irrespective of is_download=1.
+        SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, matchUri(uri, allowHidden), uri,
+                Bundle.EMPTY, null);
+
+        // We won't be able to update columns that are not part of projection map of Files table. We
+        // have already checked strict columns in previous insert operation which failed with
+        // exception. Any malicious column usage would have got caught in insert operation, hence we
+        // can safely disable strict column check for upsert.
+        qb.setStrictColumns(false);
+        return qb;
+    }
+
     private void maybePut(@NonNull ContentValues values, @NonNull String key,
             @Nullable String value) {
         if (value != null) {