Home | History | Annotate | Download | only in preference
      1 /*
      2  * Copyright (C) 2007 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 android.preference;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Collections;
     21 import java.util.List;
     22 
     23 import android.os.Handler;
     24 import android.preference.Preference.OnPreferenceChangeInternalListener;
     25 import android.view.View;
     26 import android.view.ViewGroup;
     27 import android.widget.Adapter;
     28 import android.widget.BaseAdapter;
     29 import android.widget.ListView;
     30 
     31 /**
     32  * An adapter that returns the {@link Preference} contained in this group.
     33  * In most cases, this adapter should be the base class for any custom
     34  * adapters from {@link Preference#getAdapter()}.
     35  * <p>
     36  * This adapter obeys the
     37  * {@link Preference}'s adapter rule (the
     38  * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of
     39  * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an
     40  * adapter via {@link Preference#getAdapter()}).
     41  * <p>
     42  * This adapter also propagates data change/invalidated notifications upward.
     43  * <p>
     44  * This adapter does not include this {@link PreferenceGroup} in the returned
     45  * adapter, use {@link PreferenceCategoryAdapter} instead.
     46  *
     47  * @see PreferenceCategoryAdapter
     48  */
     49 class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener {
     50 
     51     private static final String TAG = "PreferenceGroupAdapter";
     52 
     53     /**
     54      * The group that we are providing data from.
     55      */
     56     private PreferenceGroup mPreferenceGroup;
     57 
     58     /**
     59      * Maps a position into this adapter -> {@link Preference}. These
     60      * {@link Preference}s don't have to be direct children of this
     61      * {@link PreferenceGroup}, they can be grand children or younger)
     62      */
     63     private List<Preference> mPreferenceList;
     64 
     65     /**
     66      * List of unique Preference and its subclasses' names. This is used to find
     67      * out how many types of views this adapter can return. Once the count is
     68      * returned, this cannot be modified (since the ListView only checks the
     69      * count once--when the adapter is being set). We will not recycle views for
     70      * Preference subclasses seen after the count has been returned.
     71      */
     72     private ArrayList<PreferenceLayout> mPreferenceLayouts;
     73 
     74     private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
     75 
     76     /**
     77      * Blocks the mPreferenceClassNames from being changed anymore.
     78      */
     79     private boolean mHasReturnedViewTypeCount = false;
     80 
     81     private volatile boolean mIsSyncing = false;
     82 
     83     private Handler mHandler = new Handler();
     84 
     85     private Runnable mSyncRunnable = new Runnable() {
     86         public void run() {
     87             syncMyPreferences();
     88         }
     89     };
     90 
     91     private static class PreferenceLayout implements Comparable<PreferenceLayout> {
     92         private int resId;
     93         private int widgetResId;
     94         private String name;
     95 
     96         public int compareTo(PreferenceLayout other) {
     97             int compareNames = name.compareTo(other.name);
     98             if (compareNames == 0) {
     99                 if (resId == other.resId) {
    100                     if (widgetResId == other.widgetResId) {
    101                         return 0;
    102                     } else {
    103                         return widgetResId - other.widgetResId;
    104                     }
    105                 } else {
    106                     return resId - other.resId;
    107                 }
    108             } else {
    109                 return compareNames;
    110             }
    111         }
    112     }
    113 
    114     public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
    115         mPreferenceGroup = preferenceGroup;
    116         // If this group gets or loses any children, let us know
    117         mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
    118 
    119         mPreferenceList = new ArrayList<Preference>();
    120         mPreferenceLayouts = new ArrayList<PreferenceLayout>();
    121 
    122         syncMyPreferences();
    123     }
    124 
    125     private void syncMyPreferences() {
    126         synchronized(this) {
    127             if (mIsSyncing) {
    128                 return;
    129             }
    130 
    131             mIsSyncing = true;
    132         }
    133 
    134         List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
    135         flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
    136         mPreferenceList = newPreferenceList;
    137 
    138         notifyDataSetChanged();
    139 
    140         synchronized(this) {
    141             mIsSyncing = false;
    142             notifyAll();
    143         }
    144     }
    145 
    146     private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
    147         // TODO: shouldn't always?
    148         group.sortPreferences();
    149 
    150         final int groupSize = group.getPreferenceCount();
    151         for (int i = 0; i < groupSize; i++) {
    152             final Preference preference = group.getPreference(i);
    153 
    154             preferences.add(preference);
    155 
    156             if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
    157                 addPreferenceClassName(preference);
    158             }
    159 
    160             if (preference instanceof PreferenceGroup) {
    161                 final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
    162                 if (preferenceAsGroup.isOnSameScreenAsChildren()) {
    163                     flattenPreferenceGroup(preferences, preferenceAsGroup);
    164                 }
    165             }
    166 
    167             preference.setOnPreferenceChangeInternalListener(this);
    168         }
    169     }
    170 
    171     /**
    172      * Creates a string that includes the preference name, layout id and widget layout id.
    173      * If a particular preference type uses 2 different resources, they will be treated as
    174      * different view types.
    175      */
    176     private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
    177         PreferenceLayout pl = in != null? in : new PreferenceLayout();
    178         pl.name = preference.getClass().getName();
    179         pl.resId = preference.getLayoutResource();
    180         pl.widgetResId = preference.getWidgetLayoutResource();
    181         return pl;
    182     }
    183 
    184     private void addPreferenceClassName(Preference preference) {
    185         final PreferenceLayout pl = createPreferenceLayout(preference, null);
    186         int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
    187 
    188         // Only insert if it doesn't exist (when it is negative).
    189         if (insertPos < 0) {
    190             // Convert to insert index
    191             insertPos = insertPos * -1 - 1;
    192             mPreferenceLayouts.add(insertPos, pl);
    193         }
    194     }
    195 
    196     public int getCount() {
    197         return mPreferenceList.size();
    198     }
    199 
    200     public Preference getItem(int position) {
    201         if (position < 0 || position >= getCount()) return null;
    202         return mPreferenceList.get(position);
    203     }
    204 
    205     public long getItemId(int position) {
    206         if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
    207         return this.getItem(position).getId();
    208     }
    209 
    210     public View getView(int position, View convertView, ViewGroup parent) {
    211         final Preference preference = this.getItem(position);
    212         // Build a PreferenceLayout to compare with known ones that are cacheable.
    213         mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
    214 
    215         // If it's not one of the cached ones, set the convertView to null so that
    216         // the layout gets re-created by the Preference.
    217         if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
    218             convertView = null;
    219         }
    220 
    221         return preference.getView(convertView, parent);
    222     }
    223 
    224     @Override
    225     public boolean isEnabled(int position) {
    226         if (position < 0 || position >= getCount()) return true;
    227         return this.getItem(position).isSelectable();
    228     }
    229 
    230     @Override
    231     public boolean areAllItemsEnabled() {
    232         // There should always be a preference group, and these groups are always
    233         // disabled
    234         return false;
    235     }
    236 
    237     public void onPreferenceChange(Preference preference) {
    238         notifyDataSetChanged();
    239     }
    240 
    241     public void onPreferenceHierarchyChange(Preference preference) {
    242         mHandler.removeCallbacks(mSyncRunnable);
    243         mHandler.post(mSyncRunnable);
    244     }
    245 
    246     @Override
    247     public boolean hasStableIds() {
    248         return true;
    249     }
    250 
    251     @Override
    252     public int getItemViewType(int position) {
    253         if (!mHasReturnedViewTypeCount) {
    254             mHasReturnedViewTypeCount = true;
    255         }
    256 
    257         final Preference preference = this.getItem(position);
    258         if (preference.hasSpecifiedLayout()) {
    259             return IGNORE_ITEM_VIEW_TYPE;
    260         }
    261 
    262         mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
    263 
    264         int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
    265         if (viewType < 0) {
    266             // This is a class that was seen after we returned the count, so
    267             // don't recycle it.
    268             return IGNORE_ITEM_VIEW_TYPE;
    269         } else {
    270             return viewType;
    271         }
    272     }
    273 
    274     @Override
    275     public int getViewTypeCount() {
    276         if (!mHasReturnedViewTypeCount) {
    277             mHasReturnedViewTypeCount = true;
    278         }
    279 
    280         return Math.max(1, mPreferenceLayouts.size());
    281     }
    282 
    283 }
    284