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 
17 package com.android.car.dialer.ui.search;
18 
19 import android.os.Bundle;
20 import android.text.TextUtils;
21 import android.view.View;
22 
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 import androidx.fragment.app.Fragment;
26 import androidx.lifecycle.ViewModelProvider;
27 
28 import com.android.car.dialer.R;
29 import com.android.car.dialer.log.L;
30 import com.android.car.dialer.ui.common.DialerListBaseFragment;
31 import com.android.car.dialer.ui.contact.ContactDetailsFragment;
32 import com.android.car.telephony.common.Contact;
33 import com.android.car.ui.recyclerview.CarUiRecyclerView;
34 import com.android.car.ui.toolbar.Toolbar;
35 import com.android.car.ui.toolbar.ToolbarController;
36 import com.android.car.uxr.LifeCycleObserverUxrContentLimiter;
37 import com.android.car.uxr.UxrContentLimiterImpl;
38 
39 import dagger.hilt.android.AndroidEntryPoint;
40 
41 /**
42  * A fragment that will take a search query, look up contacts that match and display those
43  * results as a list.
44  */
45 @AndroidEntryPoint(DialerListBaseFragment.class)
46 public class ContactResultsFragment extends Hilt_ContactResultsFragment implements
47         Toolbar.OnSearchListener {
48 
49     /**
50      * Creates a new instance of the {@link ContactResultsFragment}.
51      *
52      * @param initialSearchQuery An optional search query that will be inputted when the fragment
53      *                           starts up.
54      */
newInstance(@ullable String initialSearchQuery)55     public static ContactResultsFragment newInstance(@Nullable String initialSearchQuery) {
56         ContactResultsFragment fragment = new ContactResultsFragment();
57         Bundle args = new Bundle();
58         args.putString(SEARCH_QUERY, initialSearchQuery);
59         fragment.setArguments(args);
60         return fragment;
61     }
62 
63     public static final String FRAGMENT_TAG = "ContactResultsFragment";
64 
65     private static final String TAG = "CD.ContactResultsFragment";
66     private static final String SEARCH_QUERY = "SearchQuery";
67 
68     private ContactResultsViewModel mContactResultsViewModel;
69     private final ContactResultsAdapter mAdapter = new ContactResultsAdapter(
70             contactResult -> onShowContactDetail(contactResult.getContact()));
71 
72     private CarUiRecyclerView.OnScrollListener mOnScrollChangeListener;
73     private ToolbarController mToolbar;
74 
75     private LifeCycleObserverUxrContentLimiter mUxrContentLimiter;
76 
77     @Override
onCreate(Bundle savedInstanceState)78     public void onCreate(Bundle savedInstanceState) {
79         super.onCreate(savedInstanceState);
80 
81         mContactResultsViewModel = new ViewModelProvider(this).get(
82                 ContactResultsViewModel.class);
83         mContactResultsViewModel.getContactSearchResults().observe(this,
84                 contactResults -> {
85                     mAdapter.setData(contactResults);
86                     showContent();
87                 });
88         mContactResultsViewModel.getSortOrderLiveData().observe(this,
89                 v -> mAdapter.setSortMethod(v));
90 
91         // Set the initial search query, if one was provided from a Intent.ACTION_SEARCH
92         if (getArguments() != null) {
93             String initialQuery = getArguments().getString(SEARCH_QUERY);
94             if (!TextUtils.isEmpty(initialQuery)) {
95                 setSearchQuery(initialQuery);
96             }
97             getArguments().clear();
98         }
99 
100         mUxrContentLimiter = new LifeCycleObserverUxrContentLimiter(
101                 new UxrContentLimiterImpl(getContext(), R.xml.uxr_config));
102         getLifecycle().addObserver(mUxrContentLimiter);
103 
104         mOnScrollChangeListener = new CarUiRecyclerView.OnScrollListener() {
105             @Override
106             public void onScrollStateChanged(@NonNull CarUiRecyclerView recyclerView,
107                                                       int newState) {
108             }
109 
110             @Override
111             public void onScrolled(@NonNull CarUiRecyclerView recyclerView, int dx, int dy) {
112                 if (dy != 0) {
113                     // Clear the focus to dismiss the keyboard in touch mode.
114                     View focusedView = getActivity().getCurrentFocus();
115                     if (focusedView != null && focusedView.isInTouchMode()) {
116                         focusedView.clearFocus();
117                     }
118                 }
119             }
120         };
121     }
122 
123     @Override
onViewCreated(View view, Bundle savedInstanceState)124     public void onViewCreated(View view, Bundle savedInstanceState) {
125         super.onViewCreated(view, savedInstanceState);
126         getRecyclerView().setAdapter(mAdapter);
127         mUxrContentLimiter.setAdapter(mAdapter);
128     }
129 
130     @Override
onDestroyView()131     public void onDestroyView() {
132         getRecyclerView().removeOnScrollListener(mOnScrollChangeListener);
133         if (mToolbar != null) {
134             mToolbar.unregisterOnSearchListener(this);
135         }
136         super.onDestroyView();
137     }
138 
139     @Override
setupToolbar(@onNull ToolbarController toolbar)140     protected void setupToolbar(@NonNull ToolbarController toolbar) {
141         super.setupToolbar(toolbar);
142         mToolbar = toolbar;
143         mToolbar.registerOnSearchListener(this);
144         mToolbar.setSearchIcon(R.drawable.ic_app_icon);
145         setSearchQuery(mContactResultsViewModel.getSearchQuery());
146 
147         if (mToolbar.canShowSearchResultsView()) {
148             mToolbar.setSearchResultsView(getRecyclerView().getView());
149         } else {
150             // Widescreen IME list should not set the scroll listener to dismiss the keyboard.
151             getRecyclerView().addOnScrollListener(mOnScrollChangeListener);
152         }
153     }
154 
155     /** Sets the search query that should be used to filter contacts. */
setSearchQuery(String query)156     public void setSearchQuery(String query) {
157         if (mToolbar != null) {
158             mToolbar.setSearchQuery(query);
159         } else {
160             onSearch(query);
161         }
162     }
163 
164     /** Triggered by search view text change. */
165     @Override
onSearch(String newQuery)166     public void onSearch(String newQuery) {
167         L.d(TAG, "onSearch: %s", newQuery);
168         mContactResultsViewModel.setSearchQuery(newQuery);
169     }
170 
onShowContactDetail(Contact contact)171     protected void onShowContactDetail(Contact contact) {
172         Fragment contactDetailsFragment = ContactDetailsFragment.newInstance(contact);
173         pushContentFragment(contactDetailsFragment, ContactDetailsFragment.FRAGMENT_TAG);
174     }
175 
176     @Override
getToolbarState()177     protected Toolbar.State getToolbarState() {
178         return Toolbar.State.SEARCH;
179     }
180 }
181