Home | History | Annotate | Download | only in dialer
      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.content.res.Resources;
     22 import android.database.ContentObserver;
     23 import android.database.Cursor;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.support.annotation.Nullable;
     30 import android.support.v4.app.Fragment;
     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 import android.widget.LinearLayout;
     37 
     38 import com.android.car.dialer.telecom.PhoneLoader;
     39 import com.android.car.dialer.telecom.UiCallManager;
     40 import com.android.car.view.PagedListView;
     41 
     42 /**
     43  * Contains a list of contacts. The call types can be any of the CALL_TYPE_* fields from
     44  * {@link PhoneLoader}.
     45  */
     46 public class StrequentsFragment extends Fragment {
     47     private static final String TAG = "Em.StrequentsFrag";
     48 
     49     private static final String KEY_MAX_CLICKS = "max_clicks";
     50     private static final int DEFAULT_MAX_CLICKS = 6;
     51 
     52     private UiCallManager mUiCallManager;
     53     private StrequentsAdapter mAdapter;
     54     private CursorLoader mSpeedialCursorLoader;
     55     private CursorLoader mCallLogCursorLoader;
     56     private Context mContext;
     57     private PagedListView mListView;
     58     private Cursor mStrequentCursor;
     59     private Cursor mCallLogCursor;
     60     private boolean mHasLoadedData;
     61 
     62     public static StrequentsFragment newInstance(UiCallManager callManager) {
     63         StrequentsFragment fragment = new StrequentsFragment();
     64         fragment.mUiCallManager = callManager;
     65         return fragment;
     66     }
     67 
     68     @Override
     69     public void onCreate(@Nullable Bundle savedInstanceState) {
     70         super.onCreate(savedInstanceState);
     71         if (Log.isLoggable(TAG, Log.DEBUG)) {
     72             Log.d(TAG, "onCreate");
     73         }
     74     }
     75 
     76     @Override
     77     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     78             Bundle savedInstanceState) {
     79         if (Log.isLoggable(TAG, Log.DEBUG)) {
     80             Log.d(TAG, "onCreateView");
     81         }
     82 
     83         mContext = getContext();
     84 
     85         View view = inflater.inflate(R.layout.strequents_fragment, container, false);
     86         mListView = (PagedListView) view.findViewById(R.id.list_view);
     87         mListView.getLayoutManager().setOffsetRows(true);
     88 
     89         mSpeedialCursorLoader = PhoneLoader.registerCallObserver(PhoneLoader.CALL_TYPE_SPEED_DIAL,
     90             mContext, (loader, cursor) -> {
     91                 if (Log.isLoggable(TAG, Log.DEBUG)) {
     92                     Log.d(TAG, "PhoneLoader: onLoadComplete (CALL_TYPE_SPEED_DIAL)");
     93                 }
     94 
     95                 onLoadStrequentCursor(cursor);
     96 
     97                 if (mContext != null) {
     98                     mListView.addItemDecoration(new Decoration(mContext));
     99                 }
    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         mListView.setLightMode();
    130         mAdapter = new StrequentsAdapter(mContext, mUiCallManager);
    131         mAdapter.setStrequentsListener(viewHolder -> {
    132             if (Log.isLoggable(TAG, Log.DEBUG)) {
    133                 Log.d(TAG, "onContactedClicked");
    134             }
    135 
    136             mUiCallManager.safePlaceCall((String) viewHolder.itemView.getTag(), false);
    137         });
    138         mListView.setMaxPages(maxPages);
    139         mListView.setAdapter(mAdapter);
    140 
    141         if (Log.isLoggable(TAG, Log.DEBUG)) {
    142             Log.d(TAG, "setItemAnimator");
    143         }
    144 
    145         mListView.getRecyclerView().setItemAnimator(new StrequentsItemAnimator());
    146         return view;
    147     }
    148 
    149     @Override
    150     public void onDestroyView() {
    151         super.onDestroyView();
    152 
    153         if (Log.isLoggable(TAG, Log.DEBUG)) {
    154             Log.d(TAG, "onDestroyView");
    155         }
    156 
    157         mAdapter.setStrequentCursor(null);
    158         mAdapter.setLastCallCursor(null);
    159         mCallLogCursorLoader.reset();
    160         mSpeedialCursorLoader.reset();
    161         mCallLogCursor = null;
    162         mStrequentCursor = null;
    163         mHasLoadedData = false;
    164         mContext = null;
    165     }
    166 
    167     private void loadDataIntoAdapter() {
    168         if (Log.isLoggable(TAG, Log.DEBUG)) {
    169             Log.d(TAG, "loadDataIntoAdapter");
    170         }
    171 
    172         mHasLoadedData = true;
    173         mAdapter.setLastCallCursor(mCallLogCursor);
    174         mAdapter.setStrequentCursor(mStrequentCursor);
    175     }
    176 
    177     private void onLoadStrequentCursor(Cursor cursor) {
    178         if (Log.isLoggable(TAG, Log.DEBUG)) {
    179             Log.d(TAG, "onLoadStrequentCursor");
    180         }
    181 
    182         if (cursor == null) {
    183             throw new IllegalArgumentException(
    184                     "cursor was null in on speed dial fetched");
    185         }
    186 
    187         mStrequentCursor = cursor;
    188         if (mCallLogCursor != null) {
    189             if (mHasLoadedData) {
    190                 mAdapter.setStrequentCursor(cursor);
    191             } else {
    192                 loadDataIntoAdapter();
    193             }
    194         }
    195     }
    196 
    197     private void onLoadCallLogCursor(Cursor cursor) {
    198         if (cursor == null) {
    199             throw new IllegalArgumentException(
    200                     "cursor was null in on calls fetched");
    201         }
    202 
    203         mCallLogCursor = cursor;
    204         if (mStrequentCursor != null) {
    205             if (mHasLoadedData) {
    206                 mAdapter.setLastCallCursor(cursor);
    207             } else {
    208                 loadDataIntoAdapter();
    209             }
    210         }
    211     }
    212 
    213     /**
    214      * A {@link ContentResolver} that is responsible for reloading the user's starred and frequent
    215      * contacts.
    216      */
    217     private class SpeedDialContentObserver extends ContentObserver {
    218         public SpeedDialContentObserver(Handler handler) {
    219             super(handler);
    220         }
    221 
    222         @Override
    223         public void onChange(boolean selfChange) {
    224             onChange(selfChange, null);
    225         }
    226 
    227         @Override
    228         public void onChange(boolean selfChange, Uri uri) {
    229             if (Log.isLoggable(TAG, Log.DEBUG)) {
    230                 Log.d(TAG, "SpeedDialContentObserver onChange() called. Reloading strequents.");
    231             }
    232             mSpeedialCursorLoader.startLoading();
    233         }
    234     }
    235 
    236     /**
    237      * A {@link ContentResolver} that is responsible for reloading the user's recent calls.
    238      */
    239     private class CallLogContentObserver extends ContentObserver {
    240         public CallLogContentObserver(Handler handler) {
    241             super(handler);
    242         }
    243 
    244         @Override
    245         public void onChange(boolean selfChange) {
    246             onChange(selfChange, null);
    247         }
    248 
    249         @Override
    250         public void onChange(boolean selfChange, Uri uri) {
    251             if (Log.isLoggable(TAG, Log.DEBUG)) {
    252                 Log.d(TAG, "CallLogContentObserver onChange() called. Reloading call log.");
    253             }
    254             mCallLogCursorLoader.startLoading();
    255         }
    256     }
    257 
    258     /**
    259      * Decoration for the speed dial cards. This ItemDecoration will not show a divider between
    260      * the dialpad item and the first speed dial item and the divider is offset but a couple of
    261      * pixels to offset the fact that the cards overlap.
    262      */
    263     private static class Decoration extends RecyclerView.ItemDecoration {
    264         private final Paint mPaint;
    265         private final int mPaintAlpha;
    266         private final int mDividerHeight;
    267 
    268         public Decoration(Context context) {
    269             Resources res = context.getResources();
    270             mPaint = new Paint();
    271             mPaint.setColor(res.getColor(R.color.car_list_divider));
    272             mDividerHeight = res.getDimensionPixelSize(R.dimen.car_divider_height);
    273             mPaintAlpha = mPaint.getAlpha();
    274         }
    275 
    276         @Override
    277         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    278             StrequentsAdapter adapter = (StrequentsAdapter) parent.getAdapter();
    279 
    280             if (adapter.getItemCount() <= 0) {
    281                 return;
    282             }
    283 
    284             final int childCount = parent.getChildCount();
    285 
    286             // Don't draw decoration line on last item of the list.
    287             for (int i = 0; i < childCount - 1; i++) {
    288                 final View child = parent.getChildAt(i);
    289 
    290                 // If the child is focused then the decoration will look bad with the focus
    291                 // highlight so don't draw it.
    292                 if (child.isFocused()) {
    293                     continue;
    294                 }
    295 
    296                 // The left edge of the divider should align with the left edge of text_container.
    297                 LinearLayout container = child.findViewById(R.id.container);
    298                 View textContainer = child.findViewById(R.id.text_container);
    299                 View card = child.findViewById(R.id.call_log_card);
    300 
    301                 int left = textContainer.getLeft() + container.getLeft() + card.getLeft();
    302                 int right = left + textContainer.getWidth();
    303 
    304                 RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
    305                 int bottom = child.getBottom() + lp.bottomMargin
    306                         + Math.round(child.getTranslationY());
    307                 int top = bottom - mDividerHeight;
    308 
    309                 if (top >= c.getHeight() || top < 0) {
    310                     break;
    311                 }
    312 
    313                 mPaint.setAlpha(Math.round(container.getAlpha() * mPaintAlpha));
    314                 c.drawRect(left, top, right, bottom, mPaint);
    315             }
    316         }
    317     }
    318 }
    319