1 /*
2  * Copyright 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.photopicker.core.selection
18 
19 import com.android.photopicker.data.model.Grantable
20 
21 /**
22  * A specialized set implementation that is aware of both user selections and pre-granted elements.
23  *
24  * This class extends the behavior of a standard set by incorporating pre-granted elements
25  * into its logic. An element is considered to be part of the set if either:
26  *
27  * 1. It has been explicitly selected by the user.
28  * 2. It is pre-granted and hasn't been explicitly de-selected by the user.
29  *
30  * **This class should be used for the following custom implementations :**
31  * - Provides an accurate `size` reflecting both selected and pre-granted elements.
32  * - `contains()` method which checks if the element should be reflected as selected on the UI.
33  *
34  * @property selection The set of elements explicitly selected by the user.
35  * @property deSelection The set of pre-granted elements that have been explicitly de-selected.
36  * @property preGrantedelementsCount The number of pre-granted elements (not including those in `deSelection`).
37  */
38 class GrantsAwareSet<T : Grantable>(
39     val selection: Set<T>,
40     val deSelection: Set<T>,
41     val preGrantedelementsCount: Int = 0,
42 ) : Set<T> {
43 
44     /**
45      * Size of the set based on current selection and preGranted elements.
46      */
47     override val size: Int = selection.size - deSelection.size + preGrantedelementsCount
48 
49     /**
50      * Checks if the set contains a specific element.
51      *
52      * This implementation considers two scenarios:
53      *
54      * 1. **Direct Presence in the Selection:**
55      *    - Returns `true` if the `element` is directly present in the current user selection.
56      *
57      * 2. **Pre-Granted Media:**
58      *    - If the `element` is a `Media` object:
59      *        - Returns `true` if the `Media` is pre-granted (via `isPreGranted()`) AND
60      *          it is not present in the deSelection set (i.e., the user has not explicitly
61      *          de-selected it).
62      *
63      * @param element The element to check for.
64      * @return `true` if the element is considered to be in the set, `false` otherwise.
65      */
containsnull66     override fun contains(element: T): Boolean {
67         // Return true if the element to be checked is part of the selection list.
68         if (selection.contains(element)) {
69             return true
70         }
71         // If the element is preGranted and is not present in the deSelection set i.e. the element
72         // has not been de-selected by the user then return true.
73         if (element.isPreGranted && !deSelection.contains(element)) {
74             return true
75         }
76         return false
77     }
78 
79     /**
80      * Returns true when:
81      * - No element has been selected by the user.
82      * - No preGranted elements are present for the current package and user.
83      * - If preGrants are present, all of them have been de-selected by the user.
84      *
85      * Returns false is all other cases.
86      */
isEmptynull87     override fun isEmpty(): Boolean {
88         return size == 0
89     }
90 
91     /**
92      * Provides an iterator to iterated only over the current user selection.
93      *
94      * Does not include preGrants.
95      */
iteratornull96     override fun iterator(): Iterator<T> {
97         return selection.iterator()
98     }
99 
100     /**
101      * Checks if all elements provided in the input are present in the set.
102      */
containsAllnull103     override fun containsAll(elements: Collection<T>): Boolean {
104         for (element in elements) {
105             if (!contains(element)) {
106                 return false
107             }
108         }
109         return true
110     }
111 }