1 /* 2 * Copyright (C) 2006 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 com.android.providers.media.LocalUriMatcher.PICKER_ID; 20 import static com.android.providers.media.util.DatabaseUtils.replaceMatchAnyChar; 21 22 import android.annotation.Nullable; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.database.Cursor; 26 import android.database.sqlite.SQLiteConstraintException; 27 import android.database.sqlite.SQLiteDatabase; 28 import android.database.sqlite.SQLiteQueryBuilder; 29 import android.net.Uri; 30 import android.provider.MediaStore; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import androidx.annotation.NonNull; 35 36 import com.android.providers.media.photopicker.PickerSyncController; 37 38 import com.google.common.annotations.VisibleForTesting; 39 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.List; 43 import java.util.Locale; 44 import java.util.Objects; 45 import java.util.stream.Collectors; 46 47 /** 48 * Manager class for the {@code media_grants} table in the {@link 49 * DatabaseHelper#EXTERNAL_DATABASE_NAME} database file. 50 * 51 * <p>Manages media grants for files in the {@code files} table based on package name. 52 */ 53 public class MediaGrants { 54 public static final String TAG = "MediaGrants"; 55 public static final String MEDIA_GRANTS_TABLE = "media_grants"; 56 public static final String FILE_ID_COLUMN = "file_id"; 57 public static final String PACKAGE_USER_ID_COLUMN = "package_user_id"; 58 public static final String GENERATION_GRANTED = "generation_granted"; 59 public static final String OWNER_PACKAGE_NAME_COLUMN = 60 MediaStore.MediaColumns.OWNER_PACKAGE_NAME; 61 // At a time for a package and userId only a limited number of grants should be held in 62 // database. 63 public static final int PER_PACKAGE_GRANTS_LIMIT_CONST = 5000; 64 65 // This should be equal to the pre-defined limit, but is modifiable for the purpose of testing. 66 @VisibleForTesting 67 public static int PER_PACKAGE_GRANTS_LIMIT = PER_PACKAGE_GRANTS_LIMIT_CONST; 68 69 private static final String CREATE_TEMPORARY_TABLE_QUERY = "CREATE TEMPORARY TABLE "; 70 private static final String MEDIA_GRANTS_AND_FILES_JOIN_TABLE_NAME = "media_grants LEFT JOIN " 71 + "files ON media_grants.file_id = files._id"; 72 73 private static final String WHERE_MEDIA_GRANTS_PACKAGE_NAME_IN = 74 "media_grants." + MediaGrants.OWNER_PACKAGE_NAME_COLUMN + " IN "; 75 76 private static final String WHERE_MEDIA_GRANTS_USER_ID = 77 "media_grants." + MediaGrants.PACKAGE_USER_ID_COLUMN + " = ? "; 78 79 private static final String WHERE_ITEM_IS_NOT_TRASHED = 80 "files." + MediaStore.Files.FileColumns.IS_TRASHED + " = ? "; 81 82 private static final String WHERE_ITEM_IS_NOT_PENDING = 83 "files." + MediaStore.Files.FileColumns.IS_PENDING + " = ? "; 84 85 private static final String WHERE_MEDIA_TYPE = 86 "files." + MediaStore.Files.FileColumns.MEDIA_TYPE + " IN "; 87 88 private static final String WHERE_MIME_TYPE = 89 "files." + MediaStore.Files.FileColumns.MIME_TYPE + " LIKE ? "; 90 91 private static final String WHERE_VOLUME_NAME_IN = 92 "files." + MediaStore.Files.FileColumns.VOLUME_NAME + " IN "; 93 94 private static final String TEMP_TABLE_NAME_FOR_DELETION = 95 "temp_table_for_media_grants_deletion"; 96 97 private static final String TEMP_TABLE_FOR_DELETION_FILE_ID_COLUMN_NAME = 98 "temp_table_for_media_grants_deletion.file_id"; 99 100 private static final String ARG_VALUE_FOR_FALSE = "0"; 101 102 private static final int VISUAL_MEDIA_TYPE_COUNT = 2; 103 private SQLiteQueryBuilder mQueryBuilder = new SQLiteQueryBuilder(); 104 private DatabaseHelper mExternalDatabase; 105 private LocalUriMatcher mUriMatcher; 106 MediaGrants(DatabaseHelper externalDatabaseHelper)107 public MediaGrants(DatabaseHelper externalDatabaseHelper) { 108 mExternalDatabase = externalDatabaseHelper; 109 mUriMatcher = new LocalUriMatcher(MediaStore.AUTHORITY); 110 mQueryBuilder.setTables(MEDIA_GRANTS_TABLE); 111 } 112 113 @VisibleForTesting setGrantsLimit(int newLimit)114 protected void setGrantsLimit(int newLimit) { 115 PER_PACKAGE_GRANTS_LIMIT = newLimit; 116 } 117 118 /** 119 * Adds media_grants for the provided URIs for the provided package name. 120 * 121 * @param packageName the package name that will receive access. 122 * @param uris list of content {@link android.net.Uri} that are recognized by 123 * mediaprovider. 124 * @param packageUserId the user_id of the package 125 */ addMediaGrantsForPackage(String packageName, List<Uri> uris, int packageUserId)126 void addMediaGrantsForPackage(String packageName, List<Uri> uris, int packageUserId) 127 throws IllegalArgumentException { 128 129 Objects.requireNonNull(packageName); 130 Objects.requireNonNull(uris); 131 132 mExternalDatabase.runWithTransaction( 133 (db) -> { 134 long generation_granted = DatabaseHelper.getGeneration(db); 135 for (Uri uri : uris) { 136 137 if (!isUriAllowed(uri)) { 138 throw new IllegalArgumentException( 139 "Illegal Uri, cannot create media grant for malformed uri: " 140 + uri.toString()); 141 } 142 143 Long id = ContentUris.parseId(uri); 144 final ContentValues values = new ContentValues(); 145 values.put(OWNER_PACKAGE_NAME_COLUMN, packageName); 146 values.put(FILE_ID_COLUMN, id); 147 values.put(PACKAGE_USER_ID_COLUMN, packageUserId); 148 values.put(GENERATION_GRANTED, generation_granted); 149 150 try { 151 mQueryBuilder.insert(db, values); 152 } catch (SQLiteConstraintException exception) { 153 // no-op 154 // this may happen due to the presence of a foreign key between the 155 // media_grants and files table. An SQLiteConstraintException 156 // exception my occur if: while inserting the grant for a file, the 157 // file itself is deleted. In this situation no operation is required. 158 } 159 } 160 161 // A clean up for older grants needs to be performed, anytime the number of 162 // grants reach more than the limit the excess grants should be removed. 163 // This is done based on order of insertion in the table. 164 SQLiteQueryBuilder sqbForGrantsCleanUp = new SQLiteQueryBuilder(); 165 sqbForGrantsCleanUp.setTables(MEDIA_GRANTS_TABLE); 166 167 String recentGrantsSubQuery = String.format( 168 Locale.ROOT, " SELECT rowid FROM %s " 169 + " WHERE %s = '%s' AND %s = %d ORDER BY rowid DESC LIMIT %d", 170 MEDIA_GRANTS_TABLE, 171 OWNER_PACKAGE_NAME_COLUMN, 172 packageName, 173 PACKAGE_USER_ID_COLUMN, 174 packageUserId, 175 PER_PACKAGE_GRANTS_LIMIT); 176 sqbForGrantsCleanUp.appendWhereStandalone( 177 "rowid NOT IN (" + recentGrantsSubQuery + ")"); 178 sqbForGrantsCleanUp.appendWhereStandalone( 179 WHERE_MEDIA_GRANTS_PACKAGE_NAME_IN + " ('" + packageName + "')"); 180 sqbForGrantsCleanUp.appendWhereStandalone( 181 PACKAGE_USER_ID_COLUMN + " = " + packageUserId); 182 int countOfGrantsDeleted = sqbForGrantsCleanUp.delete(db, null, null); 183 184 Log.d(TAG, String.format("Successfully added %s media_grants for " 185 + "package %s and packageUserId %d.", 186 uris.size(), packageName, packageUserId)); 187 Log.d(TAG, "Grants clean up : " + countOfGrantsDeleted + " deleted"); 188 189 return null; 190 }); 191 } 192 193 /** 194 * Returns the cursor for file data of items for which the passed package has READ_GRANTS with a 195 * row limit of {@link MediaGrants#PER_PACKAGE_GRANTS_LIMIT}. Any grants older than the latest 196 * {@link MediaGrants#PER_PACKAGE_GRANTS_LIMIT} number of grants are not considered. 197 * 198 * @param packageNames the package name that has access. 199 * @param packageUserId the user_id of the package. 200 * @param mimeTypes the mimeTypes of items for which the grants needs to be returned. 201 * @param availableVolumes volumes that are available, grants for items only in these volumes 202 * should be considered. 203 */ getMediaGrantsForPackages(@onNull String[] packageNames, int packageUserId, @Nullable String[] mimeTypes, @NonNull String[] availableVolumes)204 Cursor getMediaGrantsForPackages(@NonNull String[] packageNames, int packageUserId, 205 @Nullable String[] mimeTypes, @NonNull String[] availableVolumes) 206 throws IllegalArgumentException { 207 Objects.requireNonNull(packageNames); 208 return mExternalDatabase.runWithoutTransaction((db) -> { 209 final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 210 queryBuilder.setDistinct(true); 211 queryBuilder.setTables(MEDIA_GRANTS_AND_FILES_JOIN_TABLE_NAME); 212 String[] selectionArgs = buildSelectionArg(queryBuilder, 213 QueryFilterBuilder.newInstance() 214 .setPackageNameSelection(packageNames) 215 .setUserIdSelection(packageUserId) 216 .setIsNotTrashedSelection(true) 217 .setIsNotPendingSelection(true) 218 .setIsOnlyVisualMediaType(true) 219 .setMimeTypeSelection(mimeTypes) 220 .setAvailableVolumes(availableVolumes) 221 .build()); 222 223 return queryBuilder.query(db, 224 new String[]{FILE_ID_COLUMN, 225 String.format("%s.%s", MEDIA_GRANTS_TABLE, OWNER_PACKAGE_NAME_COLUMN), 226 PACKAGE_USER_ID_COLUMN}, 227 /* selection */ null, 228 /* selection args */ selectionArgs, 229 /* group by */ null, 230 /* having */ null, 231 /* sort order */ null, 232 /* limit */ String.valueOf(PER_PACKAGE_GRANTS_LIMIT), 233 /* cancellation signal */ null); 234 }); 235 } 236 removeMediaGrantsForPackage(@onNull String[] packages, @NonNull List<Uri> uris, int packageUserId)237 int removeMediaGrantsForPackage(@NonNull String[] packages, @NonNull List<Uri> uris, 238 int packageUserId) { 239 Objects.requireNonNull(packages); 240 Objects.requireNonNull(uris); 241 if (packages.length == 0) { 242 throw new IllegalArgumentException( 243 "Removing grants requires a non empty package name."); 244 } 245 246 return mExternalDatabase.runWithTransaction( 247 (db) -> { 248 // create a temporary table to be used as a selection criteria for local ids. 249 createTempTableWithLocalIdsAsColumn(uris, db); 250 251 // Create query builder and add selection args. 252 final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 253 queryBuilder.setDistinct(true); 254 queryBuilder.setTables(MEDIA_GRANTS_TABLE); 255 String[] selectionArgs = buildSelectionArg(queryBuilder, 256 QueryFilterBuilder.newInstance() 257 .setPackageNameSelection(packages) 258 .setUserIdSelection(packageUserId) 259 .setUriSelection(uris) 260 .build()); 261 // execute query. 262 int grantsRemoved = queryBuilder.delete(db, null, selectionArgs); 263 Log.d( 264 TAG, 265 String.format( 266 "Removed %s media_grants for %s user for %s.", 267 grantsRemoved, 268 String.valueOf(packageUserId), 269 Arrays.toString(packages))); 270 // Drop the temporary table. 271 deleteTempTableCreatedForLocalIdSelection(db); 272 return grantsRemoved; 273 }); 274 } 275 276 private static void createTempTableWithLocalIdsAsColumn(@NonNull List<Uri> uris, 277 @NonNull SQLiteDatabase db) { 278 279 // create a temporary table and insert the ids from received uris. 280 db.execSQL(String.format(CREATE_TEMPORARY_TABLE_QUERY + "%s (%s INTEGER)", 281 TEMP_TABLE_NAME_FOR_DELETION, FILE_ID_COLUMN)); 282 283 final SQLiteQueryBuilder queryBuilderTempTable = new SQLiteQueryBuilder(); 284 queryBuilderTempTable.setTables(TEMP_TABLE_NAME_FOR_DELETION); 285 286 List<List<Uri>> listOfSelectionArgsForId = splitArrayList(uris, 287 /* number of ids per query */ 50); 288 289 StringBuilder sb = new StringBuilder(); 290 List<Uri> selectionArgForIdSelection; 291 for (int itr = 0; itr < listOfSelectionArgsForId.size(); itr++) { 292 selectionArgForIdSelection = listOfSelectionArgsForId.get(itr); 293 if (itr == 0 || selectionArgForIdSelection.size() != listOfSelectionArgsForId.get( 294 itr - 1).size()) { 295 sb.setLength(0); 296 for (int i = 0; i < selectionArgForIdSelection.size() - 1; i++) { 297 sb.append("(?)").append(","); 298 } 299 sb.append("(?)"); 300 } 301 db.execSQL("INSERT INTO " + TEMP_TABLE_NAME_FOR_DELETION + " VALUES " + sb.toString(), 302 selectionArgForIdSelection.stream().map( 303 ContentUris::parseId).collect(Collectors.toList()).stream().toArray()); 304 } 305 } 306 307 private static <T> List<List<T>> splitArrayList(List<T> list, int chunkSize) { 308 List<List<T>> subLists = new ArrayList<>(); 309 for (int i = 0; i < list.size(); i += chunkSize) { 310 subLists.add(list.subList(i, Math.min(i + chunkSize, list.size()))); 311 } 312 return subLists; 313 } 314 315 private static void deleteTempTableCreatedForLocalIdSelection(SQLiteDatabase db) { 316 db.execSQL("DROP TABLE " + TEMP_TABLE_NAME_FOR_DELETION); 317 } 318 319 /** 320 * Removes any existing media grants for the given package from the external database. This will 321 * not alter the files or file metadata themselves. 322 * 323 * <p><strong>Note:</strong> Any files that are removed from the system because of any deletion 324 * operation or as a result of a package being uninstalled / orphaned will lead to deletion of 325 * database entry in files table. Any deletion in files table will automatically delete 326 * corresponding media_grants. 327 * 328 * <p>The action is performed for only specific {@code user}.</p> 329 * 330 * @param packages the package(s) name to clear media grants for. 331 * @param reason a logged reason why the grants are being cleared. 332 * @param user the user for which the grants need to be modified. 333 * 334 * @return the number of grants removed. 335 */ 336 int removeAllMediaGrantsForPackages(String[] packages, String reason, @NonNull Integer user) 337 throws IllegalArgumentException { 338 Objects.requireNonNull(packages); 339 if (packages.length == 0) { 340 throw new IllegalArgumentException( 341 "Removing grants requires a non empty package name."); 342 } 343 344 final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); 345 queryBuilder.setDistinct(true); 346 queryBuilder.setTables(MEDIA_GRANTS_TABLE); 347 String[] selectionArgs = buildSelectionArg(queryBuilder, QueryFilterBuilder.newInstance() 348 .setPackageNameSelection(packages) 349 .setUserIdSelection(user) 350 .build()); 351 return mExternalDatabase.runWithTransaction( 352 (db) -> { 353 int grantsRemoved = queryBuilder.delete(db, null, selectionArgs); 354 Log.d( 355 TAG, 356 String.format( 357 "Removed %s media_grants for %s user for %s. Reason: %s", 358 grantsRemoved, 359 String.valueOf(user), 360 Arrays.toString(packages), 361 reason)); 362 return grantsRemoved; 363 }); 364 } 365 366 /** 367 * Removes all existing media grants for all packages from the external database. This will not 368 * alter the files or file metadata themselves. 369 * 370 * @return the number of grants removed. 371 */ 372 int removeAllMediaGrants() { 373 return mExternalDatabase.runWithTransaction( 374 (db) -> { 375 int grantsRemoved = mQueryBuilder.delete(db, null, null); 376 Log.d(TAG, String.format("Removed %d existing media_grants", grantsRemoved)); 377 return grantsRemoved; 378 }); 379 } 380 381 /** 382 * Validates an incoming Uri to see if it's a valid media/picker uri that follows the {@link 383 * MediaProvider#PICKER_ID scheme} 384 * 385 * @return If the uri is a valid media/picker uri. 386 */ 387 private boolean isPickerUri(Uri uri) { 388 return mUriMatcher.matchUri(uri, /* allowHidden= */ false) == PICKER_ID; 389 } 390 391 /** 392 * Verifies if a URI is eligible for a media_grant. 393 * 394 * <p>Currently {@code MediaGrants} requires the file's id to be a local file. 395 * 396 * <p>This checks if the provided Uri is: 397 * 398 * <ol> 399 * <li>A Photopicker Uri 400 * <li>That the authority is the local picker authority and not a cloud provider. 401 * </ol> 402 * 403 * <p> 404 * 405 * @param uri the uri to validate 406 * @return is Allowed - true if the given Uri is supported by MediaProvider's media_grants. 407 */ 408 private boolean isUriAllowed(Uri uri) { 409 410 return isPickerUri(uri) 411 && PickerUriResolver.unwrapProviderUri(uri) 412 .getHost() 413 .equals(PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY); 414 } 415 416 /** 417 * Add required selection arguments like comparisons and WHERE checks to the 418 * {@link SQLiteQueryBuilder} qb. 419 * 420 * @param qb query builder on which the conditions/filters needs to be applied. 421 * @param queryFilter representing the types of selection arguments to be applied. 422 * @return array of selection args used to replace placeholders in query builder conditions. 423 */ 424 private String[] buildSelectionArg(SQLiteQueryBuilder qb, MediaGrantsQueryFilter queryFilter) { 425 List<String> selectArgs = new ArrayList<>(); 426 // Append where clause for package names. 427 if (queryFilter.mPackageNames != null && queryFilter.mPackageNames.length > 0) { 428 // Append the where clause for package name selection to the query builder. 429 qb.appendWhereStandalone( 430 WHERE_MEDIA_GRANTS_PACKAGE_NAME_IN + buildPlaceholderForWhereClause( 431 queryFilter.mPackageNames.length)); 432 433 // Add package names to selection args. 434 selectArgs.addAll(Arrays.asList(queryFilter.mPackageNames)); 435 } 436 437 // Append Where clause for Uris 438 if (queryFilter.mUris != null && !queryFilter.mUris.isEmpty()) { 439 // Append the where clause for local id selection to the query builder. 440 // this query would look like this example query: 441 // WHERE EXISTS (SELECT 1 from temp_table_for_media_grants_deletion WHERE 442 // temp_table_for_media_grants_deletion.file_id = media_grants.file_id) 443 qb.appendWhereStandalone(String.format("EXISTS (SELECT %s from %s WHERE %s = %s)", 444 TEMP_TABLE_FOR_DELETION_FILE_ID_COLUMN_NAME, 445 TEMP_TABLE_NAME_FOR_DELETION, 446 TEMP_TABLE_FOR_DELETION_FILE_ID_COLUMN_NAME, 447 MediaGrants.MEDIA_GRANTS_TABLE + "." + MediaGrants.FILE_ID_COLUMN)); 448 } 449 450 // Append where clause for userID. 451 if (queryFilter.mUserId != null) { 452 qb.appendWhereStandalone(WHERE_MEDIA_GRANTS_USER_ID); 453 selectArgs.add(String.valueOf(queryFilter.mUserId)); 454 } 455 456 if (queryFilter.mIsNotTrashed) { 457 qb.appendWhereStandalone(WHERE_ITEM_IS_NOT_TRASHED); 458 selectArgs.add(ARG_VALUE_FOR_FALSE); 459 } 460 461 if (queryFilter.mIsNotPending) { 462 qb.appendWhereStandalone(WHERE_ITEM_IS_NOT_PENDING); 463 selectArgs.add(ARG_VALUE_FOR_FALSE); 464 } 465 466 if (queryFilter.mIsOnlyVisualMediaType) { 467 qb.appendWhereStandalone(WHERE_MEDIA_TYPE + buildPlaceholderForWhereClause( 468 VISUAL_MEDIA_TYPE_COUNT)); 469 selectArgs.add(String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)); 470 selectArgs.add(String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)); 471 } 472 473 if (queryFilter.mAvailableVolumes != null && queryFilter.mAvailableVolumes.length > 0) { 474 qb.appendWhereStandalone( 475 WHERE_VOLUME_NAME_IN + buildPlaceholderForWhereClause( 476 queryFilter.mAvailableVolumes.length)); 477 selectArgs.addAll(Arrays.asList(queryFilter.mAvailableVolumes)); 478 } 479 480 addMimeTypesToQueryBuilderAndSelectionArgs(qb, selectArgs, queryFilter.mMimeTypeSelection); 481 482 return selectArgs.toArray(new String[selectArgs.size()]); 483 } 484 485 private void addMimeTypesToQueryBuilderAndSelectionArgs(SQLiteQueryBuilder qb, 486 List<String> selectionArgs, String[] mimeTypes) { 487 if (mimeTypes == null) { 488 return; 489 } 490 491 mimeTypes = replaceMatchAnyChar(mimeTypes); 492 ArrayList<String> whereMimeTypes = new ArrayList<>(); 493 for (String mimeType : mimeTypes) { 494 if (!TextUtils.isEmpty(mimeType)) { 495 whereMimeTypes.add(WHERE_MIME_TYPE); 496 selectionArgs.add(mimeType); 497 } 498 } 499 500 if (whereMimeTypes.isEmpty()) { 501 return; 502 } 503 qb.appendWhereStandalone(TextUtils.join(" OR ", whereMimeTypes)); 504 } 505 506 private String buildPlaceholderForWhereClause(int numberOfItemsInSelection) { 507 StringBuilder placeholder = new StringBuilder("("); 508 for (int itr = 0; itr < numberOfItemsInSelection; itr++) { 509 placeholder.append("?,"); 510 } 511 placeholder.deleteCharAt(placeholder.length() - 1); 512 placeholder.append(")"); 513 return placeholder.toString(); 514 } 515 516 static final class MediaGrantsQueryFilter { 517 518 private final List<Uri> mUris; 519 private final String[] mPackageNames; 520 private final Integer mUserId; 521 522 private final boolean mIsNotTrashed; 523 524 private final boolean mIsNotPending; 525 526 private final boolean mIsOnlyVisualMediaType; 527 private final String[] mMimeTypeSelection; 528 529 private final String[] mAvailableVolumes; 530 531 MediaGrantsQueryFilter(QueryFilterBuilder builder) { 532 this.mUris = builder.mUris; 533 this.mPackageNames = builder.mPackageNames; 534 this.mUserId = builder.mUserId; 535 this.mIsNotTrashed = builder.mIsNotTrashed; 536 this.mIsNotPending = builder.mIsNotPending; 537 this.mMimeTypeSelection = builder.mMimeTypeSelection; 538 this.mIsOnlyVisualMediaType = builder.mIsOnlyVisualMediaType; 539 this.mAvailableVolumes = builder.mAvailableVolumes; 540 } 541 } 542 543 // Static class Builder 544 static class QueryFilterBuilder { 545 546 private List<Uri> mUris; 547 private String[] mPackageNames; 548 private int mUserId; 549 550 private boolean mIsNotTrashed; 551 552 private boolean mIsNotPending; 553 554 private boolean mIsOnlyVisualMediaType; 555 private String[] mMimeTypeSelection; 556 557 private String[] mAvailableVolumes; 558 559 public static QueryFilterBuilder newInstance() { 560 return new QueryFilterBuilder(); 561 } 562 563 private QueryFilterBuilder() {} 564 565 // Setter methods 566 public QueryFilterBuilder setUriSelection(List<Uri> uris) { 567 this.mUris = uris; 568 return this; 569 } 570 571 public QueryFilterBuilder setPackageNameSelection(String[] packageNames) { 572 this.mPackageNames = packageNames; 573 return this; 574 } 575 576 public QueryFilterBuilder setUserIdSelection(int userId) { 577 this.mUserId = userId; 578 return this; 579 } 580 581 public QueryFilterBuilder setIsNotTrashedSelection(boolean isNotTrashed) { 582 this.mIsNotTrashed = isNotTrashed; 583 return this; 584 } 585 586 public QueryFilterBuilder setIsNotPendingSelection(boolean isNotPending) { 587 this.mIsNotPending = isNotPending; 588 return this; 589 } 590 591 public QueryFilterBuilder setIsOnlyVisualMediaType(boolean isOnlyVisualMediaType) { 592 this.mIsOnlyVisualMediaType = isOnlyVisualMediaType; 593 return this; 594 } 595 596 public QueryFilterBuilder setMimeTypeSelection(String[] mimeTypeSelection) { 597 this.mMimeTypeSelection = mimeTypeSelection; 598 return this; 599 } 600 601 public QueryFilterBuilder setAvailableVolumes(String[] availableVolumes) { 602 this.mAvailableVolumes = availableVolumes; 603 return this; 604 } 605 606 // build method to deal with outer class 607 // to return outer instance 608 public MediaGrantsQueryFilter build() { 609 return new MediaGrantsQueryFilter(this); 610 } 611 } 612 } 613