Merge changes from topic "apr17" into rvc-dev

* changes:
  Consistent treatment of boolean-ish values.
  Allow file movement via PermissionActivity.
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 16a0892..84b2b10 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -57,7 +57,6 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -609,6 +608,15 @@
     public static final String QUERY_ARG_RELATED_URI = "android:query-arg-related-uri";
 
     /**
+     * Flag that can be used to enable movement of media items on disk through
+     * {@link ContentResolver#update} calls. This is typically true for
+     * third-party apps, but false for system components.
+     *
+     * @hide
+     */
+    public static final String QUERY_ARG_ALLOW_MOVEMENT = "android:query-arg-allow-movement";
+
+    /**
      * Specify how {@link MediaColumns#IS_PENDING} items should be filtered when
      * performing a {@link MediaStore} operation.
      * <p>
@@ -719,7 +727,7 @@
     /** @hide */
     @Deprecated
     public static boolean getIncludePending(@NonNull Uri uri) {
-        return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_INCLUDE_PENDING));
+        return uri.getBooleanQueryParameter(MediaStore.PARAM_INCLUDE_PENDING, false);
     }
 
     /**
@@ -749,7 +757,7 @@
      * @see MediaStore#setRequireOriginal(Uri)
      */
     public static boolean getRequireOriginal(@NonNull Uri uri) {
-        return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL));
+        return uri.getBooleanQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL, false);
     }
 
     /**
@@ -3735,13 +3743,6 @@
         return volumeName;
     }
 
-    private static boolean parseBoolean(@Nullable String value) {
-        if (value == null) return false;
-        if ("1".equals(value)) return true;
-        if ("true".equalsIgnoreCase(value)) return true;
-        return false;
-    }
-
     /**
      * Uri for querying the state of the media scanner.
      */
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 3089c19..89166b6 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -2444,7 +2444,7 @@
         values.put(FileColumns.VOLUME_NAME, extractVolumeName(path));
         values.put(FileColumns.RELATIVE_PATH, extractRelativePath(path));
         values.put(FileColumns.DISPLAY_NAME, extractDisplayName(path));
-        values.put(FileColumns.IS_DOWNLOAD, isDownload(path));
+        values.put(FileColumns.IS_DOWNLOAD, isDownload(path) ? 1 : 0);
         File file = new File(path);
         if (file.exists()) {
             values.put(FileColumns.DATE_MODIFIED, file.lastModified() / 1000);
@@ -2814,7 +2814,7 @@
     private boolean maybeMarkAsDownload(@NonNull ContentValues values) {
         final String path = values.getAsString(MediaColumns.DATA);
         if (path != null && isDownload(path)) {
-            values.put(FileColumns.IS_DOWNLOAD, true);
+            values.put(FileColumns.IS_DOWNLOAD, 1);
             return true;
         }
         return false;
@@ -3130,7 +3130,7 @@
 
             case DOWNLOADS:
                 maybePut(initialValues, FileColumns.OWNER_PACKAGE_NAME, ownerPackageName);
-                initialValues.put(FileColumns.IS_DOWNLOAD, true);
+                initialValues.put(FileColumns.IS_DOWNLOAD, 1);
                 rowId = insertFile(qb, helper, match, uri, extras, initialValues,
                         FileColumns.MEDIA_TYPE_NONE, false);
                 if (rowId > 0) {
@@ -3214,14 +3214,6 @@
         }
     }
 
-    @VisibleForTesting
-    static boolean parseBoolean(String value) {
-        if (value == null) return false;
-        if ("1".equals(value)) return true;
-        if ("true".equalsIgnoreCase(value)) return true;
-        return false;
-    }
-
     @Deprecated
     private String getSharedPackages(String callingPackage) {
         final String[] sharedPackageNames = mCallingIdentity.get().getSharedPackageNames();
@@ -3270,7 +3262,7 @@
         }
 
         final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        if (parseBoolean(uri.getQueryParameter("distinct"))) {
+        if (uri.getBooleanQueryParameter("distinct", false)) {
             qb.setDistinct(true);
         }
         qb.setStrict(true);
@@ -4578,7 +4570,7 @@
                 final Uri playlistUri = ContentUris.withAppendedId(
                         MediaStore.Audio.Playlists.getContentUri(volumeName), playlistId);
 
-                if (parseBoolean(uri.getQueryParameter("move"))) {
+                if (uri.getBooleanQueryParameter("move", false)) {
                     // Convert explicit request into query; sigh, moveItem()
                     // uses zero-based indexing instead of one-based indexing
                     final int from = Integer.parseInt(uri.getPathSegments().get(5)) + 1;
@@ -4735,16 +4727,14 @@
                 break;
         }
 
-        // TODO: remove this as part of fixing b/151768142
-        final boolean isCallingPackageSystem = isCallingPackageSystem()
-                && !"com.android.systemui".equals(getCallingPackageOrSelf());
-
         // If we're touching columns that would change placement of a file,
         // blend in current values and recalculate path
+        final boolean allowMovement = extras.getBoolean(MediaStore.QUERY_ARG_ALLOW_MOVEMENT,
+                !isCallingPackageSystem());
         if (containsAny(initialValues.keySet(), sPlacementColumns)
                 && !initialValues.containsKey(MediaColumns.DATA)
-                && !isCallingPackageSystem
-                && !isThumbnail) {
+                && !isThumbnail
+                && allowMovement) {
             Trace.beginSection("movement");
 
             // We only support movement under well-defined collections
diff --git a/src/com/android/providers/media/PermissionActivity.java b/src/com/android/providers/media/PermissionActivity.java
index 0c398dd..0696ef5 100644
--- a/src/com/android/providers/media/PermissionActivity.java
+++ b/src/com/android/providers/media/PermissionActivity.java
@@ -20,6 +20,7 @@
 import static com.android.providers.media.MediaProvider.IMAGES_MEDIA_ID;
 import static com.android.providers.media.MediaProvider.VIDEO_MEDIA_ID;
 import static com.android.providers.media.MediaProvider.collectUris;
+import static com.android.providers.media.util.DatabaseUtils.getAsBoolean;
 import static com.android.providers.media.util.Logging.TAG;
 
 import android.app.Activity;
@@ -198,6 +199,7 @@
                             for (Uri uri : uris) {
                                 ops.add(ContentProviderOperation.newUpdate(uri)
                                         .withValues(values)
+                                        .withExtra(MediaStore.QUERY_ARG_ALLOW_MOVEMENT, true)
                                         .withExceptionAllowed(true)
                                         .build());
                             }
@@ -290,10 +292,10 @@
             case MediaStore.CREATE_WRITE_REQUEST_CALL:
                 return VERB_WRITE;
             case MediaStore.CREATE_TRASH_REQUEST_CALL:
-                return (values.getAsInteger(MediaColumns.IS_TRASHED) != 0)
+                return getAsBoolean(values, MediaColumns.IS_TRASHED, false)
                         ? VERB_TRASH : VERB_UNTRASH;
             case MediaStore.CREATE_FAVORITE_REQUEST_CALL:
-                return (values.getAsInteger(MediaColumns.IS_FAVORITE) != 0)
+                return getAsBoolean(values, MediaColumns.IS_FAVORITE, false)
                         ? VERB_FAVORITE : VERB_UNFAVORITE;
             case MediaStore.CREATE_DELETE_REQUEST_CALL:
                 return VERB_DELETE;
diff --git a/src/com/android/providers/media/util/DatabaseUtils.java b/src/com/android/providers/media/util/DatabaseUtils.java
index ef33b04..a5ab700 100644
--- a/src/com/android/providers/media/util/DatabaseUtils.java
+++ b/src/com/android/providers/media/util/DatabaseUtils.java
@@ -48,7 +48,9 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
+import java.util.Locale;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -532,10 +534,27 @@
         return sb.toString();
     }
 
+    public static boolean parseBoolean(@Nullable Object value, boolean def) {
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        } else if (value instanceof Number) {
+            return ((Number) value).intValue() != 0;
+        } else if (value instanceof String) {
+            final String stringValue = ((String) value).toLowerCase(Locale.ROOT);
+            return (!"false".equals(stringValue) && !"0".equals(stringValue));
+        } else {
+            return def;
+        }
+    }
+
+    public static boolean getAsBoolean(@NonNull Bundle extras,
+            @NonNull String key, boolean def) {
+        return parseBoolean(extras.get(key), def);
+    }
+
     public static boolean getAsBoolean(@NonNull ContentValues values,
             @NonNull String key, boolean def) {
-        final Integer value = values.getAsInteger(key);
-        return (value != null) ? (value != 0) : def;
+        return parseBoolean(values.get(key), def);
     }
 
     public static long getAsLong(@NonNull ContentValues values,
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index 78f2c6f..d7269ec 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -35,6 +35,7 @@
 
 import static com.android.providers.media.util.DatabaseUtils.getAsBoolean;
 import static com.android.providers.media.util.DatabaseUtils.getAsLong;
+import static com.android.providers.media.util.DatabaseUtils.parseBoolean;
 import static com.android.providers.media.util.Logging.TAG;
 
 import android.content.ClipDescription;
@@ -957,18 +958,18 @@
 
         // Only define the field when this modification is actually adjusting
         // one of the flags that should influence the expiration
-        final Integer pending = values.getAsInteger(MediaColumns.IS_PENDING);
+        final Object pending = values.get(MediaColumns.IS_PENDING);
         if (pending != null) {
-            if (pending != 0) {
+            if (parseBoolean(pending, false)) {
                 values.put(MediaColumns.DATE_EXPIRES,
                         (System.currentTimeMillis() + DEFAULT_DURATION_PENDING) / 1000);
             } else {
                 values.putNull(MediaColumns.DATE_EXPIRES);
             }
         }
-        final Integer trashed = values.getAsInteger(MediaColumns.IS_TRASHED);
+        final Object trashed = values.get(MediaColumns.IS_TRASHED);
         if (trashed != null) {
-            if (trashed != 0) {
+            if (parseBoolean(trashed, false)) {
                 values.put(MediaColumns.DATE_EXPIRES,
                         (System.currentTimeMillis() + DEFAULT_DURATION_TRASHED) / 1000);
             } else {
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
index 0f0ed64..6e9fda9 100644
--- a/tests/src/com/android/providers/media/MediaProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaProviderTest.java
@@ -741,18 +741,6 @@
     }
 
     @Test
-    public void testParseBoolean() throws Exception {
-        assertTrue(MediaProvider.parseBoolean("TRUE"));
-        assertTrue(MediaProvider.parseBoolean("true"));
-        assertTrue(MediaProvider.parseBoolean("1"));
-
-        assertFalse(MediaProvider.parseBoolean("FALSE"));
-        assertFalse(MediaProvider.parseBoolean("false"));
-        assertFalse(MediaProvider.parseBoolean("0"));
-        assertFalse(MediaProvider.parseBoolean(null));
-    }
-
-    @Test
     public void testIsDownload() throws Exception {
         assertTrue(isDownload("/storage/emulated/0/Download/colors.png"));
         assertTrue(isDownload("/storage/emulated/0/Download/test.pdf"));
diff --git a/tests/src/com/android/providers/media/util/DatabaseUtilsTest.java b/tests/src/com/android/providers/media/util/DatabaseUtilsTest.java
index 3a0ea75..d4ff968 100644
--- a/tests/src/com/android/providers/media/util/DatabaseUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/DatabaseUtilsTest.java
@@ -32,6 +32,7 @@
 import static android.database.DatabaseUtils.escapeForLike;
 
 import static com.android.providers.media.util.DatabaseUtils.maybeBalance;
+import static com.android.providers.media.util.DatabaseUtils.parseBoolean;
 import static com.android.providers.media.util.DatabaseUtils.recoverAbusiveLimit;
 import static com.android.providers.media.util.DatabaseUtils.recoverAbusiveSortOrder;
 import static com.android.providers.media.util.DatabaseUtils.resolveQueryArgs;
@@ -368,6 +369,24 @@
                 escapeForLike("/path/to/fi%le.bin"));
     }
 
+    @Test
+    public void testParseBoolean() throws Exception {
+        assertTrue(parseBoolean("TRUE", false));
+        assertTrue(parseBoolean("true", false));
+        assertTrue(parseBoolean("1", false));
+        assertTrue(parseBoolean(1, false));
+        assertTrue(parseBoolean(true, false));
+
+        assertFalse(parseBoolean("FALSE", true));
+        assertFalse(parseBoolean("false", true));
+        assertFalse(parseBoolean("0", true));
+        assertFalse(parseBoolean(0, true));
+        assertFalse(parseBoolean(false, true));
+
+        assertFalse(parseBoolean(null, false));
+        assertTrue(parseBoolean(null, true));
+    }
+
     private static Pair<String, String> recoverAbusiveGroupBy(
             Pair<String, String> selectionAndGroupBy) {
         final Bundle queryArgs = new Bundle();