1 /*
2  * Copyright 2020 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 androidx.recyclerview.widget;
18 
19 import android.util.SparseArray;
20 import android.util.SparseIntArray;
21 
22 import org.jspecify.annotations.NonNull;
23 
24 import java.util.ArrayList;
25 import java.util.List;
26 
27 /**
28  * Used by {@link ConcatAdapter} to isolate view types between nested adapters, if necessary.
29  */
30 interface ViewTypeStorage {
getWrapperForGlobalType(int globalViewType)31     @NonNull NestedAdapterWrapper getWrapperForGlobalType(int globalViewType);
32 
createViewTypeWrapper( @onNull NestedAdapterWrapper wrapper )33     @NonNull ViewTypeLookup createViewTypeWrapper(
34             @NonNull NestedAdapterWrapper wrapper
35     );
36 
37     /**
38      * Api given to {@link NestedAdapterWrapper}s.
39      */
40     interface ViewTypeLookup {
localToGlobal(int localType)41         int localToGlobal(int localType);
42 
globalToLocal(int globalType)43         int globalToLocal(int globalType);
44 
dispose()45         void dispose();
46     }
47 
48     class SharedIdRangeViewTypeStorage implements ViewTypeStorage {
49         // we keep a list of nested wrappers here even though we only need 1 to create because
50         // they might be removed.
51         SparseArray<List<NestedAdapterWrapper>> mGlobalTypeToWrapper = new SparseArray<>();
52 
53         @Override
getWrapperForGlobalType(int globalViewType)54         public @NonNull NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
55             List<NestedAdapterWrapper> nestedAdapterWrappers = mGlobalTypeToWrapper.get(
56                     globalViewType);
57             if (nestedAdapterWrappers == null || nestedAdapterWrappers.isEmpty()) {
58                 throw new IllegalArgumentException("Cannot find the wrapper for global view"
59                         + " type " + globalViewType);
60             }
61             // just return the first one since they are shared
62             return nestedAdapterWrappers.get(0);
63         }
64 
65         @Override
createViewTypeWrapper( @onNull NestedAdapterWrapper wrapper)66         public @NonNull ViewTypeLookup createViewTypeWrapper(
67                 @NonNull NestedAdapterWrapper wrapper) {
68             return new WrapperViewTypeLookup(wrapper);
69         }
70 
removeWrapper(@onNull NestedAdapterWrapper wrapper)71         void removeWrapper(@NonNull NestedAdapterWrapper wrapper) {
72             for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) {
73                 List<NestedAdapterWrapper> wrappers = mGlobalTypeToWrapper.valueAt(i);
74                 if (wrappers.remove(wrapper)) {
75                     if (wrappers.isEmpty()) {
76                         mGlobalTypeToWrapper.removeAt(i);
77                     }
78                 }
79             }
80         }
81 
82         class WrapperViewTypeLookup implements ViewTypeLookup {
83             final NestedAdapterWrapper mWrapper;
84 
WrapperViewTypeLookup(NestedAdapterWrapper wrapper)85             WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
86                 mWrapper = wrapper;
87             }
88 
89             @Override
localToGlobal(int localType)90             public int localToGlobal(int localType) {
91                 // register it first
92                 List<NestedAdapterWrapper> wrappers = mGlobalTypeToWrapper.get(
93                         localType);
94                 if (wrappers == null) {
95                     wrappers = new ArrayList<>();
96                     mGlobalTypeToWrapper.put(localType, wrappers);
97                 }
98                 if (!wrappers.contains(mWrapper)) {
99                     wrappers.add(mWrapper);
100                 }
101                 return localType;
102             }
103 
104             @Override
globalToLocal(int globalType)105             public int globalToLocal(int globalType) {
106                 return globalType;
107             }
108 
109             @Override
dispose()110             public void dispose() {
111                 removeWrapper(mWrapper);
112             }
113         }
114     }
115 
116     class IsolatedViewTypeStorage implements ViewTypeStorage {
117         SparseArray<NestedAdapterWrapper> mGlobalTypeToWrapper = new SparseArray<>();
118 
119         int mNextViewType = 0;
120 
obtainViewType(NestedAdapterWrapper wrapper)121         int obtainViewType(NestedAdapterWrapper wrapper) {
122             int nextId = mNextViewType++;
123             mGlobalTypeToWrapper.put(nextId, wrapper);
124             return nextId;
125         }
126 
127         @Override
getWrapperForGlobalType(int globalViewType)128         public @NonNull NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
129             NestedAdapterWrapper wrapper = mGlobalTypeToWrapper.get(
130                     globalViewType);
131             if (wrapper == null) {
132                 throw new IllegalArgumentException("Cannot find the wrapper for global"
133                         + " view type " + globalViewType);
134             }
135             return wrapper;
136         }
137 
138         @Override
createViewTypeWrapper( @onNull NestedAdapterWrapper wrapper)139         public @NonNull ViewTypeLookup createViewTypeWrapper(
140                 @NonNull NestedAdapterWrapper wrapper) {
141             return new WrapperViewTypeLookup(wrapper);
142         }
143 
removeWrapper(@onNull NestedAdapterWrapper wrapper)144         void removeWrapper(@NonNull NestedAdapterWrapper wrapper) {
145             for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) {
146                 NestedAdapterWrapper existingWrapper = mGlobalTypeToWrapper.valueAt(i);
147                 if (existingWrapper == wrapper) {
148                     mGlobalTypeToWrapper.removeAt(i);
149                 }
150             }
151         }
152 
153         class WrapperViewTypeLookup implements ViewTypeLookup {
154             private SparseIntArray mLocalToGlobalMapping = new SparseIntArray(1);
155             private SparseIntArray mGlobalToLocalMapping = new SparseIntArray(1);
156             final NestedAdapterWrapper mWrapper;
157 
WrapperViewTypeLookup(NestedAdapterWrapper wrapper)158             WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
159                 mWrapper = wrapper;
160             }
161 
162             @Override
localToGlobal(int localType)163             public int localToGlobal(int localType) {
164                 int index = mLocalToGlobalMapping.indexOfKey(localType);
165                 if (index > -1) {
166                     return mLocalToGlobalMapping.valueAt(index);
167                 }
168                 // get a new key.
169                 int globalType = obtainViewType(mWrapper);
170                 mLocalToGlobalMapping.put(localType, globalType);
171                 mGlobalToLocalMapping.put(globalType, localType);
172                 return globalType;
173             }
174 
175             @Override
globalToLocal(int globalType)176             public int globalToLocal(int globalType) {
177                 int index = mGlobalToLocalMapping.indexOfKey(globalType);
178                 if (index < 0) {
179                     throw new IllegalStateException("requested global type " + globalType + " does"
180                             + " not belong to the adapter:" + mWrapper.adapter);
181                 }
182                 return mGlobalToLocalMapping.valueAt(index);
183             }
184 
185             @Override
dispose()186             public void dispose() {
187                 removeWrapper(mWrapper);
188             }
189         }
190     }
191 }
192