1/*
2 * Copyright (C) 2010 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 */
16package com.android.contacts.common.list;
17
18import android.content.Context;
19import android.view.View;
20import android.view.ViewGroup;
21import com.android.common.widget.CompositeCursorAdapter;
22
23/** A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers. */
24public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
25    implements PinnedHeaderListView.PinnedHeaderAdapter {
26
27  public static final int PARTITION_HEADER_TYPE = 0;
28
29  private boolean mPinnedPartitionHeadersEnabled;
30  private boolean[] mHeaderVisibility;
31
32  public PinnedHeaderListAdapter(Context context) {
33    super(context);
34  }
35
36  public boolean getPinnedPartitionHeadersEnabled() {
37    return mPinnedPartitionHeadersEnabled;
38  }
39
40  public void setPinnedPartitionHeadersEnabled(boolean flag) {
41    this.mPinnedPartitionHeadersEnabled = flag;
42  }
43
44  @Override
45  public int getPinnedHeaderCount() {
46    if (mPinnedPartitionHeadersEnabled) {
47      return getPartitionCount();
48    } else {
49      return 0;
50    }
51  }
52
53  protected boolean isPinnedPartitionHeaderVisible(int partition) {
54    return getPinnedPartitionHeadersEnabled()
55        && hasHeader(partition)
56        && !isPartitionEmpty(partition);
57  }
58
59  /** The default implementation creates the same type of view as a normal partition header. */
60  @Override
61  public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
62    if (hasHeader(partition)) {
63      View view = null;
64      if (convertView != null) {
65        Integer headerType = (Integer) convertView.getTag();
66        if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
67          view = convertView;
68        }
69      }
70      if (view == null) {
71        view = newHeaderView(getContext(), partition, null, parent);
72        view.setTag(PARTITION_HEADER_TYPE);
73        view.setFocusable(false);
74        view.setEnabled(false);
75      }
76      bindHeaderView(view, partition, getCursor(partition));
77      view.setLayoutDirection(parent.getLayoutDirection());
78      return view;
79    } else {
80      return null;
81    }
82  }
83
84  @Override
85  public void configurePinnedHeaders(PinnedHeaderListView listView) {
86    if (!getPinnedPartitionHeadersEnabled()) {
87      return;
88    }
89
90    int size = getPartitionCount();
91
92    // Cache visibility bits, because we will need them several times later on
93    if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
94      mHeaderVisibility = new boolean[size];
95    }
96    for (int i = 0; i < size; i++) {
97      boolean visible = isPinnedPartitionHeaderVisible(i);
98      mHeaderVisibility[i] = visible;
99      if (!visible) {
100        listView.setHeaderInvisible(i, true);
101      }
102    }
103
104    int headerViewsCount = listView.getHeaderViewsCount();
105
106    // Starting at the top, find and pin headers for partitions preceding the visible one(s)
107    int maxTopHeader = -1;
108    int topHeaderHeight = 0;
109    for (int i = 0; i < size; i++) {
110      if (mHeaderVisibility[i]) {
111        int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
112        int partition = getPartitionForPosition(position);
113        if (i > partition) {
114          break;
115        }
116
117        listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
118        topHeaderHeight += listView.getPinnedHeaderHeight(i);
119        maxTopHeader = i;
120      }
121    }
122
123    // Starting at the bottom, find and pin headers for partitions following the visible one(s)
124    int maxBottomHeader = size;
125    int bottomHeaderHeight = 0;
126    int listHeight = listView.getHeight();
127    for (int i = size; --i > maxTopHeader; ) {
128      if (mHeaderVisibility[i]) {
129        int position = listView.getPositionAt(listHeight - bottomHeaderHeight) - headerViewsCount;
130        if (position < 0) {
131          break;
132        }
133
134        int partition = getPartitionForPosition(position - 1);
135        if (partition == -1 || i <= partition) {
136          break;
137        }
138
139        int height = listView.getPinnedHeaderHeight(i);
140        bottomHeaderHeight += height;
141
142        listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, false);
143        maxBottomHeader = i;
144      }
145    }
146
147    // Headers in between the top-pinned and bottom-pinned should be hidden
148    for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
149      if (mHeaderVisibility[i]) {
150        listView.setHeaderInvisible(i, isPartitionEmpty(i));
151      }
152    }
153  }
154
155  @Override
156  public int getScrollPositionForHeader(int viewIndex) {
157    return getPositionForPartition(viewIndex);
158  }
159}
160