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