1 /* 2 * Copyright (C) 2017 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 package com.android.dialer.calllog.ui; 17 18 import android.content.Context; 19 import android.database.Cursor; 20 import android.support.annotation.IntDef; 21 import android.support.annotation.Nullable; 22 import android.support.v7.widget.RecyclerView; 23 import android.support.v7.widget.RecyclerView.ViewHolder; 24 import android.view.LayoutInflater; 25 import android.view.ViewGroup; 26 import com.android.dialer.calllogutils.CallLogDates; 27 import com.android.dialer.common.Assert; 28 import com.android.dialer.glidephotomanager.GlidePhotoManager; 29 import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent; 30 import com.android.dialer.time.Clock; 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 34 /** {@link RecyclerView.Adapter} for the new call log fragment. */ 35 final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> { 36 37 /** IntDef for the different types of rows that can be shown in the call log. */ 38 @Retention(RetentionPolicy.SOURCE) 39 @IntDef({ 40 RowType.HEADER_TODAY, 41 RowType.HEADER_YESTERDAY, 42 RowType.HEADER_OLDER, 43 RowType.CALL_LOG_ENTRY 44 }) 45 @interface RowType { 46 /** Header that displays "Today". */ 47 int HEADER_TODAY = 1; 48 /** Header that displays "Yesterday". */ 49 int HEADER_YESTERDAY = 2; 50 /** Header that displays "Older". */ 51 int HEADER_OLDER = 3; 52 /** A row representing a call log entry (which could represent one or more calls). */ 53 int CALL_LOG_ENTRY = 4; 54 } 55 56 private final Clock clock; 57 private final RealtimeRowProcessor realtimeRowProcessor; 58 private final GlidePhotoManager glidePhotoManager; 59 60 private Cursor cursor; 61 62 /** Position of the "Today" header. Null when it should not be displayed. */ 63 @Nullable private Integer todayHeaderPosition; 64 65 /** Position of the "Yesterday" header. Null when it should not be displayed. */ 66 @Nullable private Integer yesterdayHeaderPosition; 67 68 /** Position of the "Older" header. Null when it should not be displayed. */ 69 @Nullable private Integer olderHeaderPosition; 70 NewCallLogAdapter(Context context, Cursor cursor, Clock clock)71 NewCallLogAdapter(Context context, Cursor cursor, Clock clock) { 72 this.cursor = cursor; 73 this.clock = clock; 74 this.realtimeRowProcessor = CallLogUiComponent.get(context).realtimeRowProcessor(); 75 this.glidePhotoManager = GlidePhotoManagerComponent.get(context).glidePhotoManager(); 76 77 setHeaderPositions(); 78 } 79 updateCursor(Cursor updatedCursor)80 void updateCursor(Cursor updatedCursor) { 81 this.cursor = updatedCursor; 82 this.realtimeRowProcessor.clearCache(); 83 84 setHeaderPositions(); 85 notifyDataSetChanged(); 86 } 87 clearCache()88 void clearCache() { 89 this.realtimeRowProcessor.clearCache(); 90 } 91 setHeaderPositions()92 private void setHeaderPositions() { 93 // If there are no rows to display, set all header positions to null. 94 if (!cursor.moveToFirst()) { 95 todayHeaderPosition = null; 96 yesterdayHeaderPosition = null; 97 olderHeaderPosition = null; 98 return; 99 } 100 101 long currentTimeMillis = clock.currentTimeMillis(); 102 103 int numItemsInToday = 0; 104 int numItemsInYesterday = 0; 105 do { 106 long timestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor); 107 long dayDifference = CallLogDates.getDayDifference(currentTimeMillis, timestamp); 108 if (dayDifference == 0) { 109 numItemsInToday++; 110 } else if (dayDifference == 1) { 111 numItemsInYesterday++; 112 } else { 113 break; 114 } 115 } while (cursor.moveToNext()); 116 117 if (numItemsInToday > 0) { 118 numItemsInToday++; // including the "Today" header; 119 } 120 if (numItemsInYesterday > 0) { 121 numItemsInYesterday++; // including the "Yesterday" header; 122 } 123 124 // Set all header positions. 125 // A header position will be null if there is no item to be displayed under that header. 126 todayHeaderPosition = numItemsInToday > 0 ? 0 : null; 127 yesterdayHeaderPosition = numItemsInYesterday > 0 ? numItemsInToday : null; 128 olderHeaderPosition = !cursor.isAfterLast() ? numItemsInToday + numItemsInYesterday : null; 129 } 130 131 @Override onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType)132 public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) { 133 switch (viewType) { 134 case RowType.HEADER_TODAY: 135 case RowType.HEADER_YESTERDAY: 136 case RowType.HEADER_OLDER: 137 return new HeaderViewHolder( 138 LayoutInflater.from(viewGroup.getContext()) 139 .inflate(R.layout.new_call_log_header, viewGroup, false)); 140 case RowType.CALL_LOG_ENTRY: 141 return new NewCallLogViewHolder( 142 LayoutInflater.from(viewGroup.getContext()) 143 .inflate(R.layout.new_call_log_entry, viewGroup, false), 144 clock, 145 realtimeRowProcessor, 146 glidePhotoManager); 147 default: 148 throw Assert.createUnsupportedOperationFailException("Unsupported view type: " + viewType); 149 } 150 } 151 152 @Override onBindViewHolder(ViewHolder viewHolder, int position)153 public void onBindViewHolder(ViewHolder viewHolder, int position) { 154 @RowType int viewType = getItemViewType(position); 155 switch (viewType) { 156 case RowType.HEADER_TODAY: 157 ((HeaderViewHolder) viewHolder).setHeader(R.string.new_call_log_header_today); 158 break; 159 case RowType.HEADER_YESTERDAY: 160 ((HeaderViewHolder) viewHolder).setHeader(R.string.new_call_log_header_yesterday); 161 break; 162 case RowType.HEADER_OLDER: 163 ((HeaderViewHolder) viewHolder).setHeader(R.string.new_call_log_header_older); 164 break; 165 case RowType.CALL_LOG_ENTRY: 166 NewCallLogViewHolder newCallLogViewHolder = (NewCallLogViewHolder) viewHolder; 167 int previousHeaders = 0; 168 if (todayHeaderPosition != null && position > todayHeaderPosition) { 169 previousHeaders++; 170 } 171 if (yesterdayHeaderPosition != null && position > yesterdayHeaderPosition) { 172 previousHeaders++; 173 } 174 if (olderHeaderPosition != null && position > olderHeaderPosition) { 175 previousHeaders++; 176 } 177 cursor.moveToPosition(position - previousHeaders); 178 newCallLogViewHolder.bind(cursor); 179 break; 180 default: 181 throw Assert.createIllegalStateFailException( 182 "Unexpected view type " + viewType + " at position: " + position); 183 } 184 } 185 186 @Override 187 @RowType getItemViewType(int position)188 public int getItemViewType(int position) { 189 if (todayHeaderPosition != null && position == todayHeaderPosition) { 190 return RowType.HEADER_TODAY; 191 } 192 if (yesterdayHeaderPosition != null && position == yesterdayHeaderPosition) { 193 return RowType.HEADER_YESTERDAY; 194 } 195 if (olderHeaderPosition != null && position == olderHeaderPosition) { 196 return RowType.HEADER_OLDER; 197 } 198 return RowType.CALL_LOG_ENTRY; 199 } 200 201 @Override getItemCount()202 public int getItemCount() { 203 int numberOfHeaders = 0; 204 if (todayHeaderPosition != null) { 205 numberOfHeaders++; 206 } 207 if (yesterdayHeaderPosition != null) { 208 numberOfHeaders++; 209 } 210 if (olderHeaderPosition != null) { 211 numberOfHeaders++; 212 } 213 return cursor.getCount() + numberOfHeaders; 214 } 215 } 216