Home | History | Annotate | Download | only in items
      1 /*
      2  * Copyright (C) 2015 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.setupwizardlib.items;
     18 
     19 import android.content.Context;
     20 import android.util.AttributeSet;
     21 import android.util.SparseIntArray;
     22 
     23 import java.util.ArrayList;
     24 import java.util.List;
     25 
     26 public class ItemGroup extends AbstractItemHierarchy implements ItemInflater.ItemParent,
     27         ItemHierarchy.Observer {
     28 
     29     /* static section */
     30 
     31     /**
     32      * Binary search for the closest value that's smaller than or equal to {@code value}, and
     33      * return the corresponding key.
     34      */
     35     private static int binarySearch(SparseIntArray array, int value) {
     36         final int size = array.size();
     37         int lo = 0;
     38         int hi = size - 1;
     39 
     40         while (lo <= hi) {
     41             final int mid = (lo + hi) >>> 1;
     42             final int midVal = array.valueAt(mid);
     43 
     44             if (midVal < value) {
     45                 lo = mid + 1;
     46             } else if (midVal > value) {
     47                 hi = mid - 1;
     48             } else {
     49                 return array.keyAt(mid);  // value found
     50             }
     51         }
     52         // Value not found. Return the last item before our search range, which is the closest
     53         // value smaller than the value we are looking for.
     54         return array.keyAt(lo - 1);
     55     }
     56 
     57     /* non-static section */
     58 
     59     private List<ItemHierarchy> mChildren = new ArrayList<>();
     60 
     61     /**
     62      * A mapping from the index of an item hierarchy in mChildren, to the first position in which
     63      * the corresponding child hierarchy represents. For example:
     64      *
     65      *   ItemHierarchy                 Item               Item Position
     66      *       Index
     67      *
     68      *         0            [         Wi-Fi AP 1       ]        0
     69      *                      |         Wi-Fi AP 2       |        1
     70      *                      |         Wi-Fi AP 3       |        2
     71      *                      |         Wi-Fi AP 4       |        3
     72      *                      [         Wi-Fi AP 5       ]        4
     73      *
     74      *         1            [  <Empty Item Hierarchy>  ]
     75      *
     76      *         2            [     Use cellular data    ]        5
     77      *
     78      *         3            [       Don't connect      ]        6
     79      *
     80      * For this example of Wi-Fi screen, the following mapping will be produced:
     81      *     [ 0 -> 0 | 2 -> 5 | 3 -> 6 ]
     82      *
     83      * Also note how ItemHierarchy index 1 is not present in the map, because it is empty.
     84      *
     85      * ItemGroup uses this map to look for which ItemHierarchy an item at a given position belongs
     86      * to.
     87      */
     88     private SparseIntArray mHierarchyStart = new SparseIntArray();
     89 
     90     private int mCount = 0;
     91     private boolean mDirty = false;
     92 
     93     public ItemGroup() {
     94         super();
     95     }
     96 
     97     public ItemGroup(Context context, AttributeSet attrs) {
     98         // Constructor for XML inflation
     99         super(context, attrs);
    100     }
    101 
    102     /**
    103      * Add a child hierarchy to this item group.
    104      */
    105     @Override
    106     public void addChild(ItemHierarchy child) {
    107         mChildren.add(child);
    108         child.registerObserver(this);
    109         onHierarchyChanged();
    110     }
    111 
    112     /**
    113      * Remove a previously added child from this item group.
    114      *
    115      * @return True if there is a match for the child and it is removed. False if the child could
    116      *         not be found in our list of child hierarchies.
    117      */
    118     public boolean removeChild(ItemHierarchy child) {
    119         if (mChildren.remove(child)) {
    120             child.unregisterObserver(this);
    121             onHierarchyChanged();
    122             return true;
    123         }
    124         return false;
    125     }
    126 
    127     /**
    128      * Remove all children from this hierarchy.
    129      */
    130     public void clear() {
    131         if (mChildren.size() == 0) {
    132             return;
    133         }
    134 
    135         for (ItemHierarchy item : mChildren) {
    136             item.unregisterObserver(this);
    137         }
    138         mChildren.clear();
    139         onHierarchyChanged();
    140     }
    141 
    142     @Override
    143     public int getCount() {
    144         updateDataIfNeeded();
    145         return mCount;
    146     }
    147 
    148     @Override
    149     public IItem getItemAt(int position) {
    150         int itemIndex = getItemIndex(position);
    151         ItemHierarchy item = mChildren.get(itemIndex);
    152         int subpos = position - mHierarchyStart.get(itemIndex);
    153         return item.getItemAt(subpos);
    154     }
    155 
    156     @Override
    157     public void onChanged(ItemHierarchy hierarchy) {
    158         // Need to set dirty, because our children may have gotten more items.
    159         mDirty = true;
    160         notifyChanged();
    161     }
    162 
    163     private void onHierarchyChanged() {
    164         onChanged(null);
    165     }
    166 
    167     @Override
    168     public ItemHierarchy findItemById(int id) {
    169         if (id == getId()) {
    170             return this;
    171         }
    172         for (ItemHierarchy child : mChildren) {
    173             ItemHierarchy childFindItem = child.findItemById(id);
    174             if (childFindItem != null) {
    175                 return childFindItem;
    176             }
    177         }
    178         return null;
    179     }
    180 
    181     /**
    182      * If dirty, this method will recalculate the number of items and mHierarchyStart.
    183      */
    184     private void updateDataIfNeeded() {
    185         if (mDirty) {
    186             mCount = 0;
    187             mHierarchyStart.clear();
    188             for (int itemIndex = 0; itemIndex < mChildren.size(); itemIndex++) {
    189                 ItemHierarchy item = mChildren.get(itemIndex);
    190                 if (item.getCount() > 0) {
    191                     mHierarchyStart.put(itemIndex, mCount);
    192                 }
    193                 mCount += item.getCount();
    194             }
    195             mDirty = false;
    196         }
    197     }
    198 
    199     /**
    200      * Use binary search to locate the item hierarchy a position is contained in.
    201      *
    202      * @return Index of the item hierarchy which is responsible for the item at {@code position}.
    203      */
    204     private int getItemIndex(int position) {
    205         updateDataIfNeeded();
    206         if (position < 0 || position >= mCount) {
    207             throw new IndexOutOfBoundsException("size=" + mCount + "; index=" + position);
    208         }
    209         int result = binarySearch(mHierarchyStart, position);
    210         if (result < 0) {
    211             throw new IllegalStateException("Cannot have item start index < 0");
    212         }
    213         return result;
    214     }
    215 }
    216