• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.providers.media;
18 
19 import static android.provider.BaseColumns._ID;
20 import static android.provider.MediaStore.Files.FileColumns._USER_ID;
21 import static android.provider.MediaStore.MediaColumns.GENERATION_MODIFIED;
22 import static android.provider.MediaStore.MediaColumns.OWNER_PACKAGE_NAME;
23 
24 import android.content.ContentUris;
25 import android.content.ContentValues;
26 import android.net.Uri;
27 import android.util.Log;
28 
29 import androidx.annotation.NonNull;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Locale;
36 
37 /**
38  * Utility class for revoking owner_grants when user deselects images that were created by the app
39  * in picker choice mode
40  */
41 public class FilesOwnershipUtils {
42 
43     private static final String FILES_TABLE_NAME = "files";
44     private static final String TEMP_TABLE_NAME = "temp_file_ids_table";
45     private static final String FILE_ID_COLUMN_NAME = "file_id";
46     private static final int CHUNK_SIZE = 50;
47     private static final String TAG = FilesOwnershipUtils.class.getSimpleName();
48 
49     private final DatabaseHelper mExternalDatabase;
50 
FilesOwnershipUtils(DatabaseHelper databaseHelper)51     public FilesOwnershipUtils(DatabaseHelper databaseHelper) {
52         mExternalDatabase = databaseHelper;
53     }
54 
55     /**
56      * Revokes the access of the file by setting the owner_package_name as null in the files table.
57      * <p>
58      * Images or videos can be preselected because the app owns the file and has access to it.
59      * If the user deselects such a image/video, we revoke the access of the file by setting the
60      * owner_package_name as null in the files table.
61      * </p>
62      */
removeOwnerPackageNameForUris(@onNull String[] packages, @NonNull List<Uri> uris, int packageUserId)63     public void removeOwnerPackageNameForUris(@NonNull String[] packages, @NonNull List<Uri> uris,
64             int packageUserId) {
65         mExternalDatabase.runWithTransaction(db -> {
66             db.execSQL("CREATE TEMPORARY TABLE " +  TEMP_TABLE_NAME + " (" + FILE_ID_COLUMN_NAME
67                     + " INTEGER)");
68 
69             /*
70              * Insert all ids in temporary tables in batches.
71              * This will be used in update query below for setting owner_package_name to null
72              * if this file is currently owned by the app
73              */
74             List<List<Uri>> uriChunks = splitArrayList(uris, CHUNK_SIZE);
75             for (List<Uri> chunk : uriChunks) {
76                 String sqlQuery = String.format(
77                         Locale.ROOT,
78                         "INSERT INTO %s (%s) VALUES %s",
79                         TEMP_TABLE_NAME,
80                         FILE_ID_COLUMN_NAME,
81                         getPlaceholderString(chunk.size())
82                 );
83 
84                 db.execSQL(sqlQuery, chunk.stream().map(ContentUris::parseId).toArray());
85             }
86 
87             long generationNumber = DatabaseHelper.getGeneration(db);
88 
89             /*
90              * sample query for setting owner_package_name as null :
91              * UPDATE files SET generation_modified = (SELECT generation from local_metadata),
92              * owner_package_name = NULL WHERE (EXISTS (SELECT file_id FROM temp_file_ids_table
93              * WHERE files_id = files._id) AND owner_package_name IN (com.example.package1,
94              * com.example.package2) AND _user_id = example_user_id)
95              */
96 
97             String whereClause = "(EXISTS (SELECT " + FILE_ID_COLUMN_NAME + " FROM "
98                     + TEMP_TABLE_NAME + " WHERE " + FILE_ID_COLUMN_NAME + " = " + FILES_TABLE_NAME
99                     + "." + _ID + ") " + "AND " + OWNER_PACKAGE_NAME + " IN ("
100                     + getPlaceholderString(packages.length) + ") " + "AND " + _USER_ID + " = ?)";
101 
102             List<String> whereArgs = new ArrayList<>(Arrays.asList(packages));
103             whereArgs.add(String.valueOf(packageUserId));
104 
105             ContentValues contentValues = new ContentValues();
106             contentValues.put(GENERATION_MODIFIED, generationNumber);
107             contentValues.putNull(OWNER_PACKAGE_NAME);
108 
109             int rowsAffected = db.update(FILES_TABLE_NAME, contentValues, whereClause,
110                     whereArgs.toArray(String[]::new));
111 
112             Log.i(TAG, "Set owner package name to null for " + rowsAffected + " items for "
113                     + "packages " + Arrays.toString(packages));
114 
115             db.execSQL("DROP TABLE " + TEMP_TABLE_NAME);
116 
117             return null;
118         });
119     }
120 
getPlaceholderString(int length)121     private static String getPlaceholderString(int length) {
122         return String.join(", ", Collections.nCopies(length, "(?)"));
123     }
124 
splitArrayList(List<T> list, int chunkSize)125     private static <T> List<List<T>> splitArrayList(List<T> list, int chunkSize) {
126         List<List<T>> subLists = new ArrayList<>();
127         for (int i = 0; i < list.size(); i += chunkSize) {
128             subLists.add(list.subList(i, Math.min(i + chunkSize, list.size())));
129         }
130         return subLists;
131     }
132 }
133