15bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam/* 25bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * Copyright (C) 2015 The Android Open Source Project 35bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * 45bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * Licensed under the Apache License, Version 2.0 (the "License"); 55bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * you may not use this file except in compliance with the License. 65bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * You may obtain a copy of the License at 75bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * 85bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * http://www.apache.org/licenses/LICENSE-2.0 95bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * 105bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * Unless required by applicable law or agreed to in writing, software 115bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * distributed under the License is distributed on an "AS IS" BASIS, 125bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * See the License for the specific language governing permissions and 145bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam * limitations under the License. 155bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam */ 165bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 175bf291fde3dfd64f264d525534730514a279c8fcMaurice Lampackage com.android.setupwizardlib.items; 185bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 195bf291fde3dfd64f264d525534730514a279c8fcMaurice Lamimport android.content.Context; 205bf291fde3dfd64f264d525534730514a279c8fcMaurice Lamimport android.util.AttributeSet; 21960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lamimport android.util.SparseIntArray; 225bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 235bf291fde3dfd64f264d525534730514a279c8fcMaurice Lamimport java.util.ArrayList; 245bf291fde3dfd64f264d525534730514a279c8fcMaurice Lamimport java.util.List; 255bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 2600358e4d12e6c7ba0f1da1fa9ad57f87da9b3b1aMaurice Lampublic class ItemGroup extends AbstractItemHierarchy implements ItemInflater.ItemParent, 2700358e4d12e6c7ba0f1da1fa9ad57f87da9b3b1aMaurice Lam ItemHierarchy.Observer { 285bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 29960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /* static section */ 30960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 31960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 32960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Binary search for the closest value that's smaller than or equal to {@code value}, and 33960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * return the corresponding key. 34960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 35960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private static int binarySearch(SparseIntArray array, int value) { 36960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam final int size = array.size(); 37960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam int lo = 0; 38960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam int hi = size - 1; 39960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 40960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam while (lo <= hi) { 41960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam final int mid = (lo + hi) >>> 1; 42960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam final int midVal = array.valueAt(mid); 43960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 44960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (midVal < value) { 45960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam lo = mid + 1; 46960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } else if (midVal > value) { 47960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam hi = mid - 1; 48960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } else { 49960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return array.keyAt(mid); // value found 50960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 51960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 52960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam // Value not found. Return the last item before our search range, which is the closest 53960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam // value smaller than the value we are looking for. 54960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return array.keyAt(lo - 1); 55960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 56960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 57960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /* non-static section */ 58960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 59960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private List<ItemHierarchy> mChildren = new ArrayList<>(); 60960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 61960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 62960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * A mapping from the index of an item hierarchy in mChildren, to the first position in which 63960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * the corresponding child hierarchy represents. For example: 64960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 65960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * ItemHierarchy Item Item Position 66960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Index 67960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 68960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 0 [ Wi-Fi AP 1 ] 0 69960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * | Wi-Fi AP 2 | 1 70960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * | Wi-Fi AP 3 | 2 71960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * | Wi-Fi AP 4 | 3 72960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * [ Wi-Fi AP 5 ] 4 73960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 74960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 1 [ <Empty Item Hierarchy> ] 75960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 76960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 2 [ Use cellular data ] 5 77960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 78960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 3 [ Don't connect ] 6 79960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 80960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * For this example of Wi-Fi screen, the following mapping will be produced: 81960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * [ 0 -> 0 | 2 -> 5 | 3 -> 6 ] 82960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 83960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Also note how ItemHierarchy index 1 is not present in the map, because it is empty. 84960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 85960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * ItemGroup uses this map to look for which ItemHierarchy an item at a given position belongs 86960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * to. 87960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 88960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private SparseIntArray mHierarchyStart = new SparseIntArray(); 89960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 90960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private int mCount = 0; 91960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private boolean mDirty = false; 92960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 93960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public ItemGroup() { 94960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam super(); 95960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 965bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 975bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam public ItemGroup(Context context, AttributeSet attrs) { 98960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam // Constructor for XML inflation 995bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam super(context, attrs); 1005bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam } 1015bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 102960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 103960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Add a child hierarchy to this item group. 104960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 10500358e4d12e6c7ba0f1da1fa9ad57f87da9b3b1aMaurice Lam @Override 106960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public void addChild(ItemHierarchy child) { 107960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mChildren.add(child); 108960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam child.registerObserver(this); 109960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam onHierarchyChanged(); 110960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 111960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 112960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 113960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Remove a previously added child from this item group. 114960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 115960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * @return True if there is a match for the child and it is removed. False if the child could 116960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * not be found in our list of child hierarchies. 117960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 118960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public boolean removeChild(ItemHierarchy child) { 119960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (mChildren.remove(child)) { 120960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam child.unregisterObserver(this); 121960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam onHierarchyChanged(); 122960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return true; 123960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 124960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return false; 125960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 126960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 127960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 128960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Remove all children from this hierarchy. 129960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 130960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public void clear() { 131960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (mChildren.size() == 0) { 132960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return; 133960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 134960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 135960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam for (ItemHierarchy item : mChildren) { 136960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam item.unregisterObserver(this); 137960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 138960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mChildren.clear(); 139960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam onHierarchyChanged(); 140960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 141960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 142960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam @Override 143960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public int getCount() { 144960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam updateDataIfNeeded(); 145960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return mCount; 146960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 147960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 148960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam @Override 149960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public IItem getItemAt(int position) { 150960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam int itemIndex = getItemIndex(position); 151960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam ItemHierarchy item = mChildren.get(itemIndex); 152960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam int subpos = position - mHierarchyStart.get(itemIndex); 153960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return item.getItemAt(subpos); 154960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 155960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 156960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam @Override 157960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public void onChanged(ItemHierarchy hierarchy) { 158960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam // Need to set dirty, because our children may have gotten more items. 159960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mDirty = true; 160960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam notifyChanged(); 161960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 162960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 163960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private void onHierarchyChanged() { 164960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam onChanged(null); 165960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 166960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 167960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam @Override 168960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam public ItemHierarchy findItemById(int id) { 169960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (id == getId()) { 170960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return this; 171960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 172960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam for (ItemHierarchy child : mChildren) { 173960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam ItemHierarchy childFindItem = child.findItemById(id); 174960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (childFindItem != null) { 175960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return childFindItem; 176960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 177960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 178960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return null; 179960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 180960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam 181960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 182960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * If dirty, this method will recalculate the number of items and mHierarchyStart. 183960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 184960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private void updateDataIfNeeded() { 185960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (mDirty) { 186960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mCount = 0; 187960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mHierarchyStart.clear(); 188960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam for (int itemIndex = 0; itemIndex < mChildren.size(); itemIndex++) { 189960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam ItemHierarchy item = mChildren.get(itemIndex); 190960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (item.getCount() > 0) { 191960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mHierarchyStart.put(itemIndex, mCount); 192960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 193960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mCount += item.getCount(); 194960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 195960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam mDirty = false; 196960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 1975bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam } 1985bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam 199960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam /** 200960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * Use binary search to locate the item hierarchy a position is contained in. 201960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * 202960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam * @return Index of the item hierarchy which is responsible for the item at {@code position}. 203960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam */ 204960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam private int getItemIndex(int position) { 205960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam updateDataIfNeeded(); 206960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (position < 0 || position >= mCount) { 207960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam throw new IndexOutOfBoundsException("size=" + mCount + "; index=" + position); 208960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 209960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam int result = binarySearch(mHierarchyStart, position); 210960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam if (result < 0) { 211960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam throw new IllegalStateException("Cannot have item start index < 0"); 212960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam } 213960c0ea0b1d36904beef0f01715dd43a211e88caMaurice Lam return result; 2145bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam } 2155bf291fde3dfd64f264d525534730514a279c8fcMaurice Lam} 216