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