1 /*
2  * Copyright (C) 2018 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.settings.datetime.timezone;
18 
19 import android.os.Bundle;
20 import android.view.LayoutInflater;
21 import android.view.Menu;
22 import android.view.MenuInflater;
23 import android.view.MenuItem;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.LinearLayout;
27 import android.widget.SearchView;
28 import android.widget.TextView;
29 
30 import androidx.annotation.NonNull;
31 import androidx.coordinatorlayout.widget.CoordinatorLayout;
32 import androidx.core.view.ViewCompat;
33 import androidx.recyclerview.widget.LinearLayoutManager;
34 import androidx.recyclerview.widget.RecyclerView;
35 
36 import com.android.settings.R;
37 import com.android.settings.core.InstrumentedFragment;
38 import com.android.settings.datetime.timezone.model.TimeZoneData;
39 import com.android.settings.datetime.timezone.model.TimeZoneDataLoader;
40 
41 import com.google.android.material.appbar.AppBarLayout;
42 
43 import java.util.Locale;
44 
45 /**
46  * It's abstract class. Subclass should use it with {@class BaseTimeZoneAdapter} and
47  * {@class AdapterItem} to provide a list view with text search capability.
48  * The search matches the prefix of words in the search text.
49  */
50 public abstract class BaseTimeZonePicker extends InstrumentedFragment
51         implements SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener {
52 
53     public static final String EXTRA_RESULT_REGION_ID =
54             "com.android.settings.datetime.timezone.result_region_id";
55     public static final String EXTRA_RESULT_TIME_ZONE_ID =
56             "com.android.settings.datetime.timezone.result_time_zone_id";
57 
58     protected AppBarLayout mAppBarLayout;
59 
60     private final int mTitleResId;
61     private final int mSearchHintResId;
62     private final boolean mSearchEnabled;
63     private final boolean mDefaultExpandSearch;
64 
65     protected Locale mLocale;
66     private BaseTimeZoneAdapter mAdapter;
67     private RecyclerView mRecyclerView;
68     private TimeZoneData mTimeZoneData;
69 
70     private SearchView mSearchView;
71 
72     /**
73      * Constructor called by subclass.
74      * @param defaultExpandSearch whether expand the search view when first launching the fragment
75      */
BaseTimeZonePicker(int titleResId, int searchHintResId, boolean searchEnabled, boolean defaultExpandSearch)76     protected BaseTimeZonePicker(int titleResId, int searchHintResId,
77             boolean searchEnabled, boolean defaultExpandSearch) {
78         mTitleResId = titleResId;
79         mSearchHintResId = searchHintResId;
80         mSearchEnabled = searchEnabled;
81         mDefaultExpandSearch = defaultExpandSearch;
82     }
83 
84     @Override
onCreate(Bundle savedInstanceState)85     public void onCreate(Bundle savedInstanceState) {
86         super.onCreate(savedInstanceState);
87         setHasOptionsMenu(true);
88         getActivity().setTitle(mTitleResId);
89     }
90 
91     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)92     public View onCreateView(LayoutInflater inflater, ViewGroup container,
93             Bundle savedInstanceState) {
94         final View view = inflater.inflate(R.layout.recycler_view, container, false);
95         mRecyclerView = view.findViewById(R.id.recycler_view);
96         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(),
97                 LinearLayoutManager.VERTICAL, /* reverseLayout */ false));
98         mRecyclerView.setAdapter(mAdapter);
99         mAppBarLayout = getActivity().findViewById(R.id.app_bar);
100         disableToolBarScrollableBehavior();
101 
102         // Initialize TimeZoneDataLoader only when mRecyclerView is ready to avoid race
103         // during onDateLoaderReady callback.
104         getLoaderManager().initLoader(0, null, new TimeZoneDataLoader.LoaderCreator(
105                 getContext(), this::onTimeZoneDataReady));
106         return view;
107     }
108 
onTimeZoneDataReady(TimeZoneData timeZoneData)109     public void onTimeZoneDataReady(TimeZoneData timeZoneData) {
110         if (mTimeZoneData == null && timeZoneData != null) {
111             mTimeZoneData = timeZoneData;
112             mAdapter = createAdapter(mTimeZoneData);
113             if (mRecyclerView != null) {
114                 mRecyclerView.setAdapter(mAdapter);
115             }
116         }
117     }
118 
getLocale()119     protected Locale getLocale() {
120         return getContext().getResources().getConfiguration().getLocales().get(0);
121     }
122 
123     /**
124      * Called when TimeZoneData is ready.
125      */
createAdapter(TimeZoneData timeZoneData)126     protected abstract BaseTimeZoneAdapter createAdapter(TimeZoneData timeZoneData);
127 
128     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)129     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
130         if (mSearchEnabled) {
131             inflater.inflate(R.menu.time_zone_base_search_menu, menu);
132 
133             final MenuItem searchMenuItem = menu.findItem(R.id.time_zone_search_menu);
134             searchMenuItem.setOnActionExpandListener(this);
135             mSearchView = (SearchView) searchMenuItem.getActionView();
136 
137             mSearchView.setQueryHint(getText(mSearchHintResId));
138             mSearchView.setOnQueryTextListener(this);
139 
140             if (mDefaultExpandSearch) {
141                 searchMenuItem.expandActionView();
142                 mSearchView.setIconified(false);
143                 mSearchView.setActivated(true);
144                 mSearchView.setQuery("", true /* submit */);
145             }
146 
147             // Set zero margin and padding to align with the text horizontally in the preference
148             final TextView searchViewView = (TextView) mSearchView.findViewById(
149                     com.android.internal.R.id.search_src_text);
150             searchViewView.setPadding(0, searchViewView.getPaddingTop(), 0,
151                     searchViewView.getPaddingBottom());
152             final View editFrame = mSearchView.findViewById(
153                     com.android.internal.R.id.search_edit_frame);
154             final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) editFrame
155                     .getLayoutParams();
156             params.setMarginStart(0);
157             params.setMarginEnd(0);
158             editFrame.setLayoutParams(params);
159         }
160     }
161 
162     @Override
onMenuItemActionExpand(MenuItem item)163     public boolean onMenuItemActionExpand(MenuItem item) {
164         // To prevent a large space on tool bar.
165         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
166         // To prevent user can expand the collapsing tool bar view.
167         ViewCompat.setNestedScrollingEnabled(mRecyclerView, false);
168         return true;
169     }
170 
171     @Override
onMenuItemActionCollapse(MenuItem item)172     public boolean onMenuItemActionCollapse(MenuItem item) {
173         // We keep the collapsed status after user cancel the search function.
174         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
175         ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
176         return true;
177     }
178 
179     @Override
onQueryTextSubmit(String query)180     public boolean onQueryTextSubmit(String query) {
181         return false;
182     }
183 
184     @Override
onQueryTextChange(String newText)185     public boolean onQueryTextChange(String newText) {
186         if (mAdapter != null) {
187             mAdapter.getFilter().filter(newText);
188         }
189         return false;
190     }
191 
192     public interface OnListItemClickListener<T extends BaseTimeZoneAdapter.AdapterItem> {
onListItemClick(T item)193         void onListItemClick(T item);
194     }
195 
disableToolBarScrollableBehavior()196     private void disableToolBarScrollableBehavior() {
197         CoordinatorLayout.LayoutParams params =
198                 (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
199         AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
200         behavior.setDragCallback(
201                 new AppBarLayout.Behavior.DragCallback() {
202                     @Override
203                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
204                         return false;
205                     }
206                 });
207         params.setBehavior(behavior);
208     }
209 }
210