|  | /* | 
|  | * Copyright (C) 2007 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.preference; | 
|  |  | 
|  | import android.compat.annotation.UnsupportedAppUsage; | 
|  | import android.graphics.drawable.Drawable; | 
|  | import android.os.Handler; | 
|  | import android.preference.Preference.OnPreferenceChangeInternalListener; | 
|  | import android.view.View; | 
|  | import android.view.ViewGroup; | 
|  | import android.widget.Adapter; | 
|  | import android.widget.BaseAdapter; | 
|  | import android.widget.FrameLayout; | 
|  | import android.widget.ListView; | 
|  |  | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collections; | 
|  | import java.util.List; | 
|  |  | 
|  | /** | 
|  | * An adapter that returns the {@link Preference} contained in this group. | 
|  | * In most cases, this adapter should be the base class for any custom | 
|  | * adapters from {@link Preference#getAdapter()}. | 
|  | * <p> | 
|  | * This adapter obeys the | 
|  | * {@link Preference}'s adapter rule (the | 
|  | * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of | 
|  | * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an | 
|  | * adapter via {@link Preference#getAdapter()}). | 
|  | * <p> | 
|  | * This adapter also propagates data change/invalidated notifications upward. | 
|  | * <p> | 
|  | * This adapter does not include this {@link PreferenceGroup} in the returned | 
|  | * adapter, use {@link PreferenceCategoryAdapter} instead. | 
|  | * | 
|  | * @see PreferenceCategoryAdapter | 
|  | * | 
|  | * @hide | 
|  | * | 
|  | * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> | 
|  | *      <a href="{@docRoot}reference/androidx/preference/package-summary.html"> | 
|  | *      Preference Library</a> for consistent behavior across all devices. For more information on | 
|  | *      using the AndroidX Preference Library see | 
|  | *      <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>. | 
|  | */ | 
|  | @Deprecated | 
|  | public class PreferenceGroupAdapter extends BaseAdapter | 
|  | implements OnPreferenceChangeInternalListener { | 
|  |  | 
|  | private static final String TAG = "PreferenceGroupAdapter"; | 
|  |  | 
|  | /** | 
|  | * The group that we are providing data from. | 
|  | */ | 
|  | private PreferenceGroup mPreferenceGroup; | 
|  |  | 
|  | /** | 
|  | * Maps a position into this adapter -> {@link Preference}. These | 
|  | * {@link Preference}s don't have to be direct children of this | 
|  | * {@link PreferenceGroup}, they can be grand children or younger) | 
|  | */ | 
|  | private List<Preference> mPreferenceList; | 
|  |  | 
|  | /** | 
|  | * List of unique Preference and its subclasses' names. This is used to find | 
|  | * out how many types of views this adapter can return. Once the count is | 
|  | * returned, this cannot be modified (since the ListView only checks the | 
|  | * count once--when the adapter is being set). We will not recycle views for | 
|  | * Preference subclasses seen after the count has been returned. | 
|  | */ | 
|  | private ArrayList<PreferenceLayout> mPreferenceLayouts; | 
|  |  | 
|  | private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout(); | 
|  |  | 
|  | /** | 
|  | * Blocks the mPreferenceClassNames from being changed anymore. | 
|  | */ | 
|  | private boolean mHasReturnedViewTypeCount = false; | 
|  |  | 
|  | private volatile boolean mIsSyncing = false; | 
|  |  | 
|  | private Handler mHandler = new Handler(); | 
|  |  | 
|  | private Runnable mSyncRunnable = new Runnable() { | 
|  | public void run() { | 
|  | syncMyPreferences(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | private int mHighlightedPosition = -1; | 
|  | private Drawable mHighlightedDrawable; | 
|  |  | 
|  | private static ViewGroup.LayoutParams sWrapperLayoutParams = new ViewGroup.LayoutParams( | 
|  | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); | 
|  |  | 
|  | private static class PreferenceLayout implements Comparable<PreferenceLayout> { | 
|  | private int resId; | 
|  | private int widgetResId; | 
|  | private String name; | 
|  |  | 
|  | public int compareTo(PreferenceLayout other) { | 
|  | int compareNames = name.compareTo(other.name); | 
|  | if (compareNames == 0) { | 
|  | if (resId == other.resId) { | 
|  | if (widgetResId == other.widgetResId) { | 
|  | return 0; | 
|  | } else { | 
|  | return widgetResId - other.widgetResId; | 
|  | } | 
|  | } else { | 
|  | return resId - other.resId; | 
|  | } | 
|  | } else { | 
|  | return compareNames; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) { | 
|  | mPreferenceGroup = preferenceGroup; | 
|  | // If this group gets or loses any children, let us know | 
|  | mPreferenceGroup.setOnPreferenceChangeInternalListener(this); | 
|  |  | 
|  | mPreferenceList = new ArrayList<Preference>(); | 
|  | mPreferenceLayouts = new ArrayList<PreferenceLayout>(); | 
|  |  | 
|  | syncMyPreferences(); | 
|  | } | 
|  |  | 
|  | private void syncMyPreferences() { | 
|  | synchronized(this) { | 
|  | if (mIsSyncing) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | mIsSyncing = true; | 
|  | } | 
|  |  | 
|  | List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size()); | 
|  | flattenPreferenceGroup(newPreferenceList, mPreferenceGroup); | 
|  | mPreferenceList = newPreferenceList; | 
|  |  | 
|  | notifyDataSetChanged(); | 
|  |  | 
|  | synchronized(this) { | 
|  | mIsSyncing = false; | 
|  | notifyAll(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) { | 
|  | // TODO: shouldn't always? | 
|  | group.sortPreferences(); | 
|  |  | 
|  | final int groupSize = group.getPreferenceCount(); | 
|  | for (int i = 0; i < groupSize; i++) { | 
|  | final Preference preference = group.getPreference(i); | 
|  |  | 
|  | preferences.add(preference); | 
|  |  | 
|  | if (!mHasReturnedViewTypeCount && preference.isRecycleEnabled()) { | 
|  | addPreferenceClassName(preference); | 
|  | } | 
|  |  | 
|  | if (preference instanceof PreferenceGroup) { | 
|  | final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference; | 
|  | if (preferenceAsGroup.isOnSameScreenAsChildren()) { | 
|  | flattenPreferenceGroup(preferences, preferenceAsGroup); | 
|  | } | 
|  | } | 
|  |  | 
|  | preference.setOnPreferenceChangeInternalListener(this); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a string that includes the preference name, layout id and widget layout id. | 
|  | * If a particular preference type uses 2 different resources, they will be treated as | 
|  | * different view types. | 
|  | */ | 
|  | private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) { | 
|  | PreferenceLayout pl = in != null? in : new PreferenceLayout(); | 
|  | pl.name = preference.getClass().getName(); | 
|  | pl.resId = preference.getLayoutResource(); | 
|  | pl.widgetResId = preference.getWidgetLayoutResource(); | 
|  | return pl; | 
|  | } | 
|  |  | 
|  | private void addPreferenceClassName(Preference preference) { | 
|  | final PreferenceLayout pl = createPreferenceLayout(preference, null); | 
|  | int insertPos = Collections.binarySearch(mPreferenceLayouts, pl); | 
|  |  | 
|  | // Only insert if it doesn't exist (when it is negative). | 
|  | if (insertPos < 0) { | 
|  | // Convert to insert index | 
|  | insertPos = insertPos * -1 - 1; | 
|  | mPreferenceLayouts.add(insertPos, pl); | 
|  | } | 
|  | } | 
|  |  | 
|  | public int getCount() { | 
|  | return mPreferenceList.size(); | 
|  | } | 
|  |  | 
|  | @UnsupportedAppUsage | 
|  | public Preference getItem(int position) { | 
|  | if (position < 0 || position >= getCount()) return null; | 
|  | return mPreferenceList.get(position); | 
|  | } | 
|  |  | 
|  | public long getItemId(int position) { | 
|  | if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID; | 
|  | return this.getItem(position).getId(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @hide | 
|  | */ | 
|  | public void setHighlighted(int position) { | 
|  | mHighlightedPosition = position; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @hide | 
|  | */ | 
|  | public void setHighlightedDrawable(Drawable drawable) { | 
|  | mHighlightedDrawable = drawable; | 
|  | } | 
|  |  | 
|  | public View getView(int position, View convertView, ViewGroup parent) { | 
|  | final Preference preference = this.getItem(position); | 
|  | // Build a PreferenceLayout to compare with known ones that are cacheable. | 
|  | mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); | 
|  |  | 
|  | // If it's not one of the cached ones, set the convertView to null so that | 
|  | // the layout gets re-created by the Preference. | 
|  | if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0 || | 
|  | (getItemViewType(position) == getHighlightItemViewType())) { | 
|  | convertView = null; | 
|  | } | 
|  | View result = preference.getView(convertView, parent); | 
|  | if (position == mHighlightedPosition && mHighlightedDrawable != null) { | 
|  | ViewGroup wrapper = new FrameLayout(parent.getContext()); | 
|  | wrapper.setLayoutParams(sWrapperLayoutParams); | 
|  | wrapper.setBackgroundDrawable(mHighlightedDrawable); | 
|  | wrapper.addView(result); | 
|  | result = wrapper; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isEnabled(int position) { | 
|  | if (position < 0 || position >= getCount()) return true; | 
|  | return this.getItem(position).isSelectable(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean areAllItemsEnabled() { | 
|  | // There should always be a preference group, and these groups are always | 
|  | // disabled | 
|  | return false; | 
|  | } | 
|  |  | 
|  | public void onPreferenceChange(Preference preference) { | 
|  | notifyDataSetChanged(); | 
|  | } | 
|  |  | 
|  | public void onPreferenceHierarchyChange(Preference preference) { | 
|  | mHandler.removeCallbacks(mSyncRunnable); | 
|  | mHandler.post(mSyncRunnable); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean hasStableIds() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private int getHighlightItemViewType() { | 
|  | return getViewTypeCount() - 1; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getItemViewType(int position) { | 
|  | if (position == mHighlightedPosition) { | 
|  | return getHighlightItemViewType(); | 
|  | } | 
|  |  | 
|  | if (!mHasReturnedViewTypeCount) { | 
|  | mHasReturnedViewTypeCount = true; | 
|  | } | 
|  |  | 
|  | final Preference preference = this.getItem(position); | 
|  | if (!preference.isRecycleEnabled()) { | 
|  | return IGNORE_ITEM_VIEW_TYPE; | 
|  | } | 
|  |  | 
|  | mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); | 
|  |  | 
|  | int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout); | 
|  | if (viewType < 0) { | 
|  | // This is a class that was seen after we returned the count, so | 
|  | // don't recycle it. | 
|  | return IGNORE_ITEM_VIEW_TYPE; | 
|  | } else { | 
|  | return viewType; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getViewTypeCount() { | 
|  | if (!mHasReturnedViewTypeCount) { | 
|  | mHasReturnedViewTypeCount = true; | 
|  | } | 
|  |  | 
|  | return Math.max(1, mPreferenceLayouts.size()) + 1; | 
|  | } | 
|  |  | 
|  | } |