1 /* 2 * Copyright (C) 2015 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.car.dialer; 17 18 import android.content.ContentResolver; 19 import android.content.Context; 20 import android.content.CursorLoader; 21 import android.database.ContentObserver; 22 import android.database.Cursor; 23 import android.graphics.Rect; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.support.annotation.NonNull; 28 import android.support.annotation.Nullable; 29 import android.support.v4.app.Fragment; 30 import android.support.v7.widget.GridLayoutManager; 31 import android.support.v7.widget.RecyclerView; 32 import android.util.Log; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 37 import androidx.car.widget.DayNightStyle; 38 import androidx.car.widget.PagedListView; 39 40 import com.android.car.dialer.telecom.PhoneLoader; 41 import com.android.car.dialer.telecom.UiCallManager; 42 43 /** 44 * Contains a list of contacts. The call types can be any of the CALL_TYPE_* fields from 45 * {@link PhoneLoader}. 46 */ 47 public class StrequentsFragment extends Fragment { 48 private static final String TAG = "Em.StrequentsFrag"; 49 50 private static final String KEY_MAX_CLICKS = "max_clicks"; 51 private static final int DEFAULT_MAX_CLICKS = 6; 52 53 private UiCallManager mUiCallManager; 54 private StrequentsAdapter mAdapter; 55 private CursorLoader mSpeedialCursorLoader; 56 private CursorLoader mCallLogCursorLoader; 57 private Context mContext; 58 private PagedListView mListView; 59 private Cursor mStrequentCursor; 60 private Cursor mCallLogCursor; 61 private boolean mHasLoadedData; 62 newInstance()63 public static StrequentsFragment newInstance() { 64 return new StrequentsFragment(); 65 } 66 67 @Override onCreate(@ullable Bundle savedInstanceState)68 public void onCreate(@Nullable Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 if (Log.isLoggable(TAG, Log.DEBUG)) { 71 Log.d(TAG, "onCreate"); 72 } 73 } 74 75 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)76 public View onCreateView(LayoutInflater inflater, ViewGroup container, 77 Bundle savedInstanceState) { 78 if (Log.isLoggable(TAG, Log.DEBUG)) { 79 Log.d(TAG, "onCreateView"); 80 } 81 82 mContext = getContext(); 83 mUiCallManager = UiCallManager.get(); 84 85 View view = inflater.inflate(R.layout.strequents_fragment, container, false); 86 mListView = view.findViewById(R.id.list_view); 87 int numOfColumn = getContext().getResources().getInteger( 88 R.integer.favorite_fragment_grid_column); 89 mListView.getRecyclerView().setLayoutManager( 90 new GridLayoutManager(getContext(), numOfColumn)); 91 mListView.getRecyclerView().addItemDecoration(new ItemSpacingDecoration()); 92 93 mSpeedialCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_SPEED_DIAL, 94 mContext, (loader, cursor) -> { 95 if (Log.isLoggable(TAG, Log.DEBUG)) { 96 Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_SPEED_DIAL)"); 97 } 98 99 onLoadStrequentCursor(cursor); 100 }); 101 102 // Get the latest call log from the call logs history. 103 mCallLogCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_ALL, mContext, 104 (loader, cursor) -> { 105 if (Log.isLoggable(TAG, Log.DEBUG)) { 106 Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_ALL)"); 107 } 108 onLoadCallLogCursor(cursor); 109 }); 110 111 ContentResolver contentResolver = mContext.getContentResolver(); 112 contentResolver.registerContentObserver(mSpeedialCursorLoader.getUri(), 113 false, new SpeedDialContentObserver(new Handler())); 114 contentResolver.registerContentObserver(mCallLogCursorLoader.getUri(), 115 false, new CallLogContentObserver(new Handler())); 116 117 // Maximum number of forward acting clicks the user can perform 118 Bundle args = getArguments(); 119 int maxClicks = args == null 120 ? DEFAULT_MAX_CLICKS 121 : args.getInt(KEY_MAX_CLICKS, DEFAULT_MAX_CLICKS /* G.maxForwardClicks.get() */); 122 // We want to show one fewer page than max clicks to allow clicking on an item, 123 // but, the first page is "free" since it doesn't take any clicks to show 124 final int maxPages = maxClicks < 0 ? -1 : maxClicks; 125 if (Log.isLoggable(TAG, Log.VERBOSE)) { 126 Log.v(TAG, "Max clicks: " + maxClicks + ", Max pages: " + maxPages); 127 } 128 129 mAdapter = new StrequentsAdapter(mContext, mUiCallManager); 130 mAdapter.setStrequentsListener(viewHolder -> { 131 if (Log.isLoggable(TAG, Log.DEBUG)) { 132 Log.d(TAG, "onContactedClicked"); 133 } 134 135 mUiCallManager.safePlaceCall((String) viewHolder.itemView.getTag(), false); 136 }); 137 mListView.setMaxPages(maxPages); 138 mListView.setAdapter(mAdapter); 139 140 if (Log.isLoggable(TAG, Log.DEBUG)) { 141 Log.d(TAG, "setItemAnimator"); 142 } 143 144 mListView.getRecyclerView().setItemAnimator(null); 145 return view; 146 } 147 148 @Override onDestroyView()149 public void onDestroyView() { 150 super.onDestroyView(); 151 152 if (Log.isLoggable(TAG, Log.DEBUG)) { 153 Log.d(TAG, "onDestroyView"); 154 } 155 156 mAdapter.setStrequentCursor(null); 157 mAdapter.setLastCallCursor(null); 158 mCallLogCursorLoader.reset(); 159 mSpeedialCursorLoader.reset(); 160 mCallLogCursor = null; 161 mStrequentCursor = null; 162 mHasLoadedData = false; 163 mContext = null; 164 } 165 loadDataIntoAdapter()166 private void loadDataIntoAdapter() { 167 if (Log.isLoggable(TAG, Log.DEBUG)) { 168 Log.d(TAG, "loadDataIntoAdapter"); 169 } 170 171 mHasLoadedData = true; 172 mAdapter.setLastCallCursor(mCallLogCursor); 173 mAdapter.setStrequentCursor(mStrequentCursor); 174 } 175 onLoadStrequentCursor(Cursor cursor)176 private void onLoadStrequentCursor(Cursor cursor) { 177 if (Log.isLoggable(TAG, Log.DEBUG)) { 178 Log.d(TAG, "onLoadStrequentCursor"); 179 } 180 181 if (cursor == null) { 182 throw new IllegalArgumentException( 183 "cursor was null in on speed dial fetched"); 184 } 185 186 mStrequentCursor = cursor; 187 if (mCallLogCursor != null) { 188 if (mHasLoadedData) { 189 mAdapter.setStrequentCursor(cursor); 190 } else { 191 loadDataIntoAdapter(); 192 } 193 } 194 } 195 onLoadCallLogCursor(Cursor cursor)196 private void onLoadCallLogCursor(Cursor cursor) { 197 if (cursor == null) { 198 throw new IllegalArgumentException( 199 "cursor was null in on calls fetched"); 200 } 201 202 mCallLogCursor = cursor; 203 if (mStrequentCursor != null) { 204 if (mHasLoadedData) { 205 mAdapter.setLastCallCursor(cursor); 206 } else { 207 loadDataIntoAdapter(); 208 } 209 } 210 } 211 212 /** 213 * A {@link ContentResolver} that is responsible for reloading the user's starred and frequent 214 * contacts. 215 */ 216 private class SpeedDialContentObserver extends ContentObserver { SpeedDialContentObserver(Handler handler)217 public SpeedDialContentObserver(Handler handler) { 218 super(handler); 219 } 220 221 @Override onChange(boolean selfChange)222 public void onChange(boolean selfChange) { 223 onChange(selfChange, null); 224 } 225 226 @Override onChange(boolean selfChange, Uri uri)227 public void onChange(boolean selfChange, Uri uri) { 228 if (Log.isLoggable(TAG, Log.DEBUG)) { 229 Log.d(TAG, "SpeedDialContentObserver onChange() called. Reloading strequents."); 230 } 231 mSpeedialCursorLoader.startLoading(); 232 } 233 } 234 235 /** 236 * A {@link ContentResolver} that is responsible for reloading the user's recent calls. 237 */ 238 private class CallLogContentObserver extends ContentObserver { CallLogContentObserver(Handler handler)239 public CallLogContentObserver(Handler handler) { 240 super(handler); 241 } 242 243 @Override onChange(boolean selfChange)244 public void onChange(boolean selfChange) { 245 onChange(selfChange, null); 246 } 247 248 @Override onChange(boolean selfChange, Uri uri)249 public void onChange(boolean selfChange, Uri uri) { 250 if (Log.isLoggable(TAG, Log.DEBUG)) { 251 Log.d(TAG, "CallLogContentObserver onChange() called. Reloading call log."); 252 } 253 mCallLogCursorLoader.startLoading(); 254 } 255 } 256 257 private class ItemSpacingDecoration extends RecyclerView.ItemDecoration { 258 259 @Override getItemOffsets(@onNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)260 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, 261 @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 262 super.getItemOffsets(outRect, view, parent, state); 263 int carPadding1 = mContext.getResources().getDimensionPixelOffset( 264 R.dimen.car_padding_1); 265 266 int leftPadding = 0; 267 int rightPadding = 0; 268 if (parent.getChildAdapterPosition(view) % 2 == 0) { 269 rightPadding = carPadding1; 270 } else { 271 leftPadding = carPadding1; 272 } 273 274 outRect.set(leftPadding, carPadding1, rightPadding, carPadding1); 275 } 276 } 277 } 278