blob: 39ec2d354b8ae5c35bb49bc920476d04b450098a [file] [log] [blame]
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -07001/*
Dmitri Plotnikovfcfdffb2010-07-15 18:00:55 -07002 * Copyright (C) 2010 The Android Open Source Project
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -07003 *
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
17package com.android.contacts.activities;
18
Wenyi Wang11ff39a2016-08-07 15:46:03 -070019import android.animation.ArgbEvaluator;
Brian Attwell3c763b32014-07-21 14:25:28 -070020import android.animation.ValueAnimator;
Brian Attwell20510ec2015-02-27 16:10:45 -080021import android.app.Activity;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070022import android.content.Context;
Wenyi Wangdb662692016-07-14 16:07:44 -070023import android.content.res.ColorStateList;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070024import android.os.Bundle;
Wenyi Wang23d9b6e2015-11-19 15:09:06 -080025import android.support.v4.content.ContextCompat;
Wenyi Wang3c3b6f12016-04-02 13:58:43 -070026import android.support.v7.app.ActionBar;
27import android.support.v7.widget.Toolbar;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070028import android.text.Editable;
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070029import android.text.TextUtils;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070030import android.text.TextWatcher;
Brian Attwell20510ec2015-02-27 16:10:45 -080031import android.view.Gravity;
Brian Attwell3c763b32014-07-21 14:25:28 -070032import android.view.LayoutInflater;
Katherine Kuan1ee9df62011-06-11 16:54:01 -070033import android.view.View;
Gary Mai0a49afa2016-12-05 15:53:58 -080034import android.view.View.OnClickListener;
Brian Attwell3c763b32014-07-21 14:25:28 -070035import android.view.ViewGroup;
Wenyi Wangdb662692016-07-14 16:07:44 -070036import android.view.ViewTreeObserver;
Wenyi Wang11ff39a2016-08-07 15:46:03 -070037import android.view.Window;
Katherine Kuand8a61b72011-07-27 13:57:13 -070038import android.view.inputmethod.InputMethodManager;
Gary Mai0a49afa2016-12-05 15:53:58 -080039import android.widget.EditText;
Brian Attwell20510ec2015-02-27 16:10:45 -080040import android.widget.FrameLayout;
Wenyi Wangdb662692016-07-14 16:07:44 -070041import android.widget.ImageView;
Brian Attwell20510ec2015-02-27 16:10:45 -080042import android.widget.LinearLayout.LayoutParams;
Dmitri Plotnikov1a59cff2010-09-18 12:50:45 -070043import android.widget.SearchView.OnCloseListener;
Brian Attwell20510ec2015-02-27 16:10:45 -080044import android.widget.TextView;
Dmitri Plotnikov92a6fdf2010-07-09 13:19:33 -070045
Wenyi Wang92d7e512016-10-03 14:57:19 -070046import com.android.contacts.ContactsDrawerActivity;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070047import com.android.contacts.R;
48import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
Gary Mai69c182a2016-12-05 13:07:03 -080049import com.android.contacts.compat.CompatUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070050import com.android.contacts.list.ContactsRequest;
Gary Mai0a49afa2016-12-05 15:53:58 -080051import com.android.contacts.util.MaterialColorMapUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070052
Wenyi Wangdb662692016-07-14 16:07:44 -070053import java.util.ArrayList;
54
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070055/**
Dmitri Plotnikovfcfdffb2010-07-15 18:00:55 -070056 * Adapter for the action bar at the top of the Contacts activity.
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070057 */
Brian Attwell6adbf1e2014-05-30 16:44:51 -070058public class ActionBarAdapter implements OnCloseListener {
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070059
60 public interface Listener {
Daniel Lehmann311882a2012-04-27 22:31:38 -070061 public abstract class Action {
62 public static final int CHANGE_SEARCH_QUERY = 0;
63 public static final int START_SEARCH_MODE = 1;
Brian Attwell20510ec2015-02-27 16:10:45 -080064 public static final int START_SELECTION_MODE = 2;
65 public static final int STOP_SEARCH_AND_SELECTION_MODE = 3;
Brian Attwell865847b2015-04-20 14:40:17 -070066 public static final int BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE = 4;
Katherine Kuanb5760b92011-06-01 16:19:40 -070067 }
68
Daniel Lehmann311882a2012-04-27 22:31:38 -070069 void onAction(int action);
Makoto Onuki4d788fc2011-07-12 10:00:10 -070070
Brian Attwell6adbf1e2014-05-30 16:44:51 -070071 void onUpButtonPressed();
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070072 }
73
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070074 private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070075 private static final String EXTRA_KEY_QUERY = "navBar.query";
Brian Attwell20510ec2015-02-27 16:10:45 -080076 private static final String EXTRA_KEY_SELECTED_MODE = "navBar.selectionMode";
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070077
Brian Attwell20510ec2015-02-27 16:10:45 -080078 private boolean mSelectionMode;
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070079 private boolean mSearchMode;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070080 private String mQueryString;
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070081
Brian Attwell6adbf1e2014-05-30 16:44:51 -070082 private EditText mSearchView;
Andrew Leead6e0ee2015-05-05 17:47:42 -070083 private View mClearSearchView;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070084 private View mSearchContainer;
Brian Attwell20510ec2015-02-27 16:10:45 -080085 private View mSelectionContainer;
Dmitri Plotnikov92a6fdf2010-07-09 13:19:33 -070086
Brian Attwell3c763b32014-07-21 14:25:28 -070087 private int mMaxToolbarContentInsetStart;
Wenyi Wangbd114972016-07-22 10:42:40 -070088 private int mActionBarAnimationDuration;
Brian Attwell3c763b32014-07-21 14:25:28 -070089
Brian Attwell20510ec2015-02-27 16:10:45 -080090 private final Activity mActivity;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070091
92 private Listener mListener;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070093
Makoto Onuki4d788fc2011-07-12 10:00:10 -070094 private final ActionBar mActionBar;
Brian Attwell3c763b32014-07-21 14:25:28 -070095 private final Toolbar mToolbar;
Brian Attwell20510ec2015-02-27 16:10:45 -080096 /**
97 * Frame that contains the toolbar and draws the toolbar's background color. This is useful
98 * for placing things behind the toolbar.
99 */
100 private final FrameLayout mToolBarFrame;
Dmitri Plotnikov8b826462010-11-17 14:52:24 -0800101
Makoto Onukie0c66af2011-07-13 14:24:03 -0700102 private boolean mShowHomeIcon;
Walter Jang59087d32016-05-17 13:52:15 -0700103 private boolean mShowHomeAsUp;
Makoto Onukie0c66af2011-07-13 14:24:03 -0700104
Walter Jang68a0be72016-05-20 10:36:28 -0700105 private int mSearchHintResId;
106
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700107 private ValueAnimator mStatusBarAnimator;
108
Brian Attwell20510ec2015-02-27 16:10:45 -0800109 public ActionBarAdapter(Activity activity, Listener listener, ActionBar actionBar,
Wenyi Wangca6018d2016-08-18 17:23:13 -0700110 Toolbar toolbar) {
111 this(activity, listener, actionBar, toolbar, R.string.hint_findContacts);
Walter Jang68a0be72016-05-20 10:36:28 -0700112 }
113
114 public ActionBarAdapter(Activity activity, Listener listener, ActionBar actionBar,
Wenyi Wangca6018d2016-08-18 17:23:13 -0700115 Toolbar toolbar, int searchHintResId) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800116 mActivity = activity;
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700117 mListener = listener;
118 mActionBar = actionBar;
Brian Attwell3c763b32014-07-21 14:25:28 -0700119 mToolbar = toolbar;
Brian Attwell20510ec2015-02-27 16:10:45 -0800120 mToolBarFrame = (FrameLayout) mToolbar.getParent();
Brian Attwell3c763b32014-07-21 14:25:28 -0700121 mMaxToolbarContentInsetStart = mToolbar.getContentInsetStart();
Walter Jang68a0be72016-05-20 10:36:28 -0700122 mSearchHintResId = searchHintResId;
Wenyi Wangbd114972016-07-22 10:42:40 -0700123 mActionBarAnimationDuration =
124 mActivity.getResources().getInteger(R.integer.action_bar_animation_duration);
Makoto Onukie0c66af2011-07-13 14:24:03 -0700125
Brian Attwell20510ec2015-02-27 16:10:45 -0800126 setupSearchAndSelectionViews();
Maurice Chu7f5220e2012-05-14 10:00:28 -0700127 }
128
Walter Jang59087d32016-05-17 13:52:15 -0700129 public void setShowHomeIcon(boolean showHomeIcon) {
130 mShowHomeIcon = showHomeIcon;
131 }
132
133 public void setShowHomeAsUp(boolean showHomeAsUp) {
134 mShowHomeAsUp = showHomeAsUp;
135 }
136
Wenyi Wangf5493a62016-07-11 14:28:52 -0700137 public View getSelectionContainer() {
138 return mSelectionContainer;
139 }
140
Brian Attwell20510ec2015-02-27 16:10:45 -0800141 private void setupSearchAndSelectionViews() {
Brian Attwell3c763b32014-07-21 14:25:28 -0700142 final LayoutInflater inflater = (LayoutInflater) mToolbar.getContext().getSystemService(
143 Context.LAYOUT_INFLATER_SERVICE);
Brian Attwell20510ec2015-02-27 16:10:45 -0800144
145 // Setup search bar
Brian Attwell3c763b32014-07-21 14:25:28 -0700146 mSearchContainer = inflater.inflate(R.layout.search_bar_expanded, mToolbar,
147 /* attachToRoot = */ false);
148 mSearchContainer.setVisibility(View.VISIBLE);
149 mToolbar.addView(mSearchContainer);
Brian Attwell20510ec2015-02-27 16:10:45 -0800150 mSearchContainer.setBackgroundColor(mActivity.getResources().getColor(
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700151 R.color.searchbox_background_color));
152 mSearchView = (EditText) mSearchContainer.findViewById(R.id.search_view);
Walter Jang68a0be72016-05-20 10:36:28 -0700153 mSearchView.setHint(mActivity.getString(mSearchHintResId));
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700154 mSearchView.addTextChangedListener(new SearchTextWatcher());
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700155 mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(
156 new OnClickListener() {
157 @Override
158 public void onClick(View v) {
159 if (mListener != null) {
160 mListener.onUpButtonPressed();
161 }
162 }
163 });
Brian Attwell20510ec2015-02-27 16:10:45 -0800164
Andrew Leead6e0ee2015-05-05 17:47:42 -0700165 mClearSearchView = mSearchContainer.findViewById(R.id.search_close_button);
166 mClearSearchView.setOnClickListener(
167 new OnClickListener() {
168 @Override
169 public void onClick(View v) {
170 setQueryString(null);
171 }
172 });
173
Brian Attwell20510ec2015-02-27 16:10:45 -0800174 // Setup selection bar
175 mSelectionContainer = inflater.inflate(R.layout.selection_bar, mToolbar,
176 /* attachToRoot = */ false);
177 // Insert the selection container into mToolBarFrame behind the Toolbar, so that
178 // the Toolbar's MenuItems can appear on top of the selection container.
179 mToolBarFrame.addView(mSelectionContainer, 0);
180 mSelectionContainer.findViewById(R.id.selection_close).setOnClickListener(
181 new OnClickListener() {
182 @Override
183 public void onClick(View v) {
184 if (mListener != null) {
185 mListener.onUpButtonPressed();
186 }
187 }
188 });
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700189 }
190
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700191 public void initialize(Bundle savedState, ContactsRequest request) {
192 if (savedState == null) {
193 mSearchMode = request.isSearchMode();
194 mQueryString = request.getQueryString();
Brian Attwelld2962a32015-03-02 14:48:50 -0800195 mSelectionMode = false;
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700196 } else {
197 mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
Brian Attwell20510ec2015-02-27 16:10:45 -0800198 mSelectionMode = savedState.getBoolean(EXTRA_KEY_SELECTED_MODE);
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700199 mQueryString = savedState.getString(EXTRA_KEY_QUERY);
Brian Attwell26dc5592014-02-07 17:10:03 -0800200 }
Katherine Kuan3c877e32012-01-03 17:27:36 -0800201 // Show tabs or the expanded {@link SearchView}, depending on whether or not we are in
202 // search mode.
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700203 update(true /* skipAnimation */);
Katherine Kuan3c877e32012-01-03 17:27:36 -0800204 // Expanding the {@link SearchView} clears the query, so set the query from the
205 // {@link ContactsRequest} after it has been expanded, if applicable.
206 if (mSearchMode && !TextUtils.isEmpty(mQueryString)) {
207 setQueryString(mQueryString);
208 }
Dmitri Plotnikov8b826462010-11-17 14:52:24 -0800209 }
210
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700211 public void setListener(Listener listener) {
212 mListener = listener;
213 }
214
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700215 private class SearchTextWatcher implements TextWatcher {
216
217 @Override
218 public void onTextChanged(CharSequence queryString, int start, int before, int count) {
219 if (queryString.equals(mQueryString)) {
220 return;
221 }
222 mQueryString = queryString.toString();
223 if (!mSearchMode) {
224 if (!TextUtils.isEmpty(queryString)) {
225 setSearchMode(true);
226 }
227 } else if (mListener != null) {
228 mListener.onAction(Action.CHANGE_SEARCH_QUERY);
229 }
Andrew Leead6e0ee2015-05-05 17:47:42 -0700230 mClearSearchView.setVisibility(
231 TextUtils.isEmpty(queryString) ? View.GONE : View.VISIBLE);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700232 }
233
234 @Override
235 public void afterTextChanged(Editable s) {}
236
237 @Override
238 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
239 }
240
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700241 /**
Makoto Onuki1db00f62011-10-19 12:40:12 -0700242 * @return Whether in search mode, i.e. if the search view is visible/expanded.
243 *
244 * Note even if the action bar is in search mode, if the query is empty, the search fragment
245 * will not be in search mode.
246 */
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700247 public boolean isSearchMode() {
248 return mSearchMode;
249 }
250
Brian Attwell20510ec2015-02-27 16:10:45 -0800251 /**
252 * @return Whether in selection mode, i.e. if the selection view is visible/expanded.
253 */
254 public boolean isSelectionMode() {
255 return mSelectionMode;
256 }
257
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700258 public void setSearchMode(boolean flag) {
259 if (mSearchMode != flag) {
260 mSearchMode = flag;
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700261 update(false /* skipAnimation */);
Katherine Kuanf3f933a2011-06-10 11:34:54 -0700262 if (mSearchView == null) {
263 return;
264 }
Dmitri Plotnikov0b51a8d2010-10-21 21:22:52 -0700265 if (mSearchMode) {
Brian Attwell87901e02014-12-01 19:11:31 -0800266 mSearchView.setEnabled(true);
Makoto Onuki4ba903c2011-07-07 16:55:17 -0700267 setFocusOnSearchView();
Brian Attwell87901e02014-12-01 19:11:31 -0800268 } else {
269 // Disable search view, so that it doesn't keep the IME visible.
270 mSearchView.setEnabled(false);
Dmitri Plotnikov0b51a8d2010-10-21 21:22:52 -0700271 }
Brian Attwellb3944dc2014-11-18 20:53:59 -0800272 setQueryString(null);
Daniel Lehmann311882a2012-04-27 22:31:38 -0700273 } else if (flag) {
274 // Everything is already set up. Still make sure the keyboard is up
275 if (mSearchView != null) setFocusOnSearchView();
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700276 }
277 }
278
Brian Attwell20510ec2015-02-27 16:10:45 -0800279 public void setSelectionMode(boolean flag) {
280 if (mSelectionMode != flag) {
281 mSelectionMode = flag;
282 update(false /* skipAnimation */);
283 }
284 }
285
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700286 public String getQueryString() {
Makoto Onuki1db00f62011-10-19 12:40:12 -0700287 return mSearchMode ? mQueryString : null;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700288 }
289
290 public void setQueryString(String query) {
291 mQueryString = query;
Katherine Kuanf3f933a2011-06-10 11:34:54 -0700292 if (mSearchView != null) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700293 mSearchView.setText(query);
Brian Attwellb3944dc2014-11-18 20:53:59 -0800294 // When programmatically entering text into the search view, the most reasonable
295 // place for the cursor is after all the text.
296 mSearchView.setSelection(mSearchView.getText() == null ?
297 0 : mSearchView.getText().length());
Katherine Kuanf3f933a2011-06-10 11:34:54 -0700298 }
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700299 }
300
Makoto Onukie0c66af2011-07-13 14:24:03 -0700301 /** @return true if the "UP" icon is showing. */
302 public boolean isUpShowing() {
303 return mSearchMode; // Only shown on the search mode.
304 }
305
Brian Attwell3c763b32014-07-21 14:25:28 -0700306 private void updateDisplayOptionsInner() {
Makoto Onukie0c66af2011-07-13 14:24:03 -0700307 // All the flags we may change in this method.
308 final int MASK = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME
Brian Attwell20510ec2015-02-27 16:10:45 -0800309 | ActionBar.DISPLAY_HOME_AS_UP;
Makoto Onukie0c66af2011-07-13 14:24:03 -0700310
311 // The current flags set to the action bar. (only the ones that we may change here)
312 final int current = mActionBar.getDisplayOptions() & MASK;
313
Brian Attwell20510ec2015-02-27 16:10:45 -0800314 final boolean isSearchOrSelectionMode = mSearchMode || mSelectionMode;
315
Makoto Onukie0c66af2011-07-13 14:24:03 -0700316 // Build the new flags...
317 int newFlags = 0;
Brian Attwell20510ec2015-02-27 16:10:45 -0800318 if (mShowHomeIcon && !isSearchOrSelectionMode) {
Makoto Onukie0c66af2011-07-13 14:24:03 -0700319 newFlags |= ActionBar.DISPLAY_SHOW_HOME;
Walter Jang59087d32016-05-17 13:52:15 -0700320 if (mShowHomeAsUp) {
321 newFlags |= ActionBar.DISPLAY_HOME_AS_UP;
322 }
Makoto Onukie0c66af2011-07-13 14:24:03 -0700323 }
Brian Attwell865847b2015-04-20 14:40:17 -0700324 if (mSearchMode && !mSelectionMode) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800325 // The search container is placed inside the toolbar. So we need to disable the
326 // Toolbar's content inset in order to allow the search container to be the width of
327 // the window.
Brian Attwell3c763b32014-07-21 14:25:28 -0700328 mToolbar.setContentInsetsRelative(0, mToolbar.getContentInsetEnd());
Brian Attwell20510ec2015-02-27 16:10:45 -0800329 }
330 if (!isSearchOrSelectionMode) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700331 newFlags |= ActionBar.DISPLAY_SHOW_TITLE;
Brian Attwell3c763b32014-07-21 14:25:28 -0700332 mToolbar.setContentInsetsRelative(mMaxToolbarContentInsetStart,
333 mToolbar.getContentInsetEnd());
Wenyi Wangf568b302016-04-02 13:58:43 -0700334 mToolbar.setNavigationIcon(R.drawable.ic_menu_hamburger);
335 } else {
336 mToolbar.setNavigationIcon(null);
Makoto Onukie0c66af2011-07-13 14:24:03 -0700337 }
Brian Attwellb0d3c762014-05-22 03:44:22 +0000338
Brian Attwell20510ec2015-02-27 16:10:45 -0800339 if (mSelectionMode) {
340 // Minimize the horizontal width of the Toolbar since the selection container is placed
341 // behind the toolbar and its left hand side needs to be clickable.
342 FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mToolbar.getLayoutParams();
343 params.width = LayoutParams.WRAP_CONTENT;
344 params.gravity = Gravity.END;
345 mToolbar.setLayoutParams(params);
346 } else {
347 FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mToolbar.getLayoutParams();
348 params.width = LayoutParams.MATCH_PARENT;
349 params.gravity = Gravity.END;
350 mToolbar.setLayoutParams(params);
351 }
Brian Attwell3c763b32014-07-21 14:25:28 -0700352
Makoto Onukie0c66af2011-07-13 14:24:03 -0700353 if (current != newFlags) {
354 // Pass the mask here to preserve other flags that we're not interested here.
355 mActionBar.setDisplayOptions(newFlags, MASK);
356 }
357 }
358
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700359 private void update(boolean skipAnimation) {
Wenyi Wangdb662692016-07-14 16:07:44 -0700360 updateOverflowButtonColor();
361
Brian Attwell20510ec2015-02-27 16:10:45 -0800362 final boolean isSelectionModeChanging
363 = (mSelectionContainer.getParent() == null) == mSelectionMode;
Brian Attwell865847b2015-04-20 14:40:17 -0700364 final boolean isSwitchingFromSearchToSelection =
365 mSearchMode && isSelectionModeChanging || mSearchMode && mSelectionMode;
Brian Attwell20510ec2015-02-27 16:10:45 -0800366 final boolean isSearchModeChanging
Brian Attwell3c763b32014-07-21 14:25:28 -0700367 = (mSearchContainer.getParent() == null) == mSearchMode;
Brian Attwell20510ec2015-02-27 16:10:45 -0800368 final boolean isTabHeightChanging = isSearchModeChanging || isSelectionModeChanging;
369
Wenyi Wang92d7e512016-10-03 14:57:19 -0700370 // Update toolbar and status bar color.
371 mToolBarFrame.setBackgroundColor(MaterialColorMapUtils.getToolBarColor(mActivity));
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700372 updateStatusBarColor(isSelectionModeChanging && !isSearchModeChanging);
373
Brian Attwell20510ec2015-02-27 16:10:45 -0800374 // When skipAnimation=true, it is possible that we will switch from search mode
375 // to selection mode directly. So we need to remove the undesired container in addition
376 // to adding the desired container.
Brian Attwell865847b2015-04-20 14:40:17 -0700377 if (skipAnimation || isSwitchingFromSearchToSelection) {
378 if (isTabHeightChanging || isSwitchingFromSearchToSelection) {
Brian Attwell865847b2015-04-20 14:40:17 -0700379 mToolbar.removeView(mSearchContainer);
380 mToolBarFrame.removeView(mSelectionContainer);
381 if (mSelectionMode) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800382 addSelectionContainer();
Brian Attwell865847b2015-04-20 14:40:17 -0700383 } else if (mSearchMode) {
Brian Attwell865847b2015-04-20 14:40:17 -0700384 addSearchContainer();
Brian Attwell20510ec2015-02-27 16:10:45 -0800385 }
386 updateDisplayOptions(isSearchModeChanging);
387 }
388 return;
389 }
390
391 // Handle a switch to/from selection mode, due to UI interaction.
392 if (isSelectionModeChanging) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800393 if (mSelectionMode) {
394 addSelectionContainer();
395 mSelectionContainer.setAlpha(0);
Wenyi Wangbd114972016-07-22 10:42:40 -0700396 mSelectionContainer.animate().alpha(1).setDuration(mActionBarAnimationDuration);
Brian Attwell20510ec2015-02-27 16:10:45 -0800397 updateDisplayOptions(isSearchModeChanging);
398 } else {
Brian Attwell865847b2015-04-20 14:40:17 -0700399 if (mListener != null) {
400 mListener.onAction(Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE);
401 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800402 mSelectionContainer.setAlpha(1);
Wenyi Wangbd114972016-07-22 10:42:40 -0700403 mSelectionContainer.animate().alpha(0).setDuration(mActionBarAnimationDuration)
404 .withEndAction(new Runnable() {
Brian Attwell20510ec2015-02-27 16:10:45 -0800405 @Override
406 public void run() {
407 updateDisplayOptions(isSearchModeChanging);
Brian Attwell20510ec2015-02-27 16:10:45 -0800408 mToolBarFrame.removeView(mSelectionContainer);
409 }
410 });
411 }
412 }
413
414 // Handle a switch to/from search mode, due to UI interaction.
415 if (isSearchModeChanging) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700416 if (mSearchMode) {
Brian Attwell3c763b32014-07-21 14:25:28 -0700417 addSearchContainer();
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700418 mSearchContainer.setAlpha(0);
Wenyi Wangbd114972016-07-22 10:42:40 -0700419 mSearchContainer.animate().alpha(1).setDuration(mActionBarAnimationDuration);
Brian Attwell20510ec2015-02-27 16:10:45 -0800420 updateDisplayOptions(isSearchModeChanging);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700421 } else {
422 mSearchContainer.setAlpha(1);
Wenyi Wangbd114972016-07-22 10:42:40 -0700423 mSearchContainer.animate().alpha(0).setDuration(mActionBarAnimationDuration)
424 .withEndAction(new Runnable() {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700425 @Override
426 public void run() {
Brian Attwell20510ec2015-02-27 16:10:45 -0800427 updateDisplayOptions(isSearchModeChanging);
Brian Attwell3c763b32014-07-21 14:25:28 -0700428 mToolbar.removeView(mSearchContainer);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700429 }
430 });
431 }
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700432 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800433 }
434
Wenyi Wangdb662692016-07-14 16:07:44 -0700435 /**
436 * Find overflow menu ImageView by its content description and update its color.
437 */
438 private void updateOverflowButtonColor() {
439 final String overflowDescription = mActivity.getResources().getString(
440 R.string.abc_action_menu_overflow_description);
441 final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
442 final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
443 viewTreeObserver.addOnGlobalLayoutListener(
444 new ViewTreeObserver.OnGlobalLayoutListener() {
445 @Override
446 public void onGlobalLayout() {
447 // Find the overflow ImageView.
448 final ArrayList<View> outViews = new ArrayList<>();
449 decorView.findViewsWithText(outViews, overflowDescription,
450 View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
Wenyi Wangdb662692016-07-14 16:07:44 -0700451
Wenyi Wang44702ac2016-12-01 13:31:22 -0800452 for (View view : outViews) {
453 if (!(view instanceof ImageView)) {
454 continue;
455 }
456 final ImageView overflow = (ImageView) view;
457
458 // Update the overflow image color.
459 final int iconColor;
460 if (mSelectionMode) {
461 iconColor = mActivity.getResources().getColor(
462 R.color.actionbar_color_grey_solid);
463 } else {
464 iconColor = mActivity.getResources().getColor(
465 R.color.actionbar_text_color);
466 }
467 overflow.setImageTintList(ColorStateList.valueOf(iconColor));
Wenyi Wangdb662692016-07-14 16:07:44 -0700468 }
Wenyi Wangdb662692016-07-14 16:07:44 -0700469
470 // We're done, remove the listener.
471 decorView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
472 }
473 });
474 }
475
Brian Attwell20510ec2015-02-27 16:10:45 -0800476 public void setSelectionCount(int selectionCount) {
477 TextView textView = (TextView) mSelectionContainer.findViewById(R.id.selection_count_text);
478 if (selectionCount == 0) {
479 textView.setVisibility(View.GONE);
480 } else {
481 textView.setVisibility(View.VISIBLE);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700482 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800483 textView.setText(String.valueOf(selectionCount));
484 }
485
Wenyi Wang64a6bc42016-07-07 18:34:48 -0700486 public void setActionBarTitle(String title) {
487 final TextView textView =
488 (TextView) mSelectionContainer.findViewById(R.id.selection_count_text);
489 textView.setVisibility(View.VISIBLE);
490 textView.setText(title);
491 }
492
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700493 private void updateStatusBarColor(boolean shouldAnimate) {
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800494 if (!CompatUtils.isLollipopCompatible()) {
495 return; // we can't change the status bar color prior to Lollipop
496 }
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700497
Brian Attwell20510ec2015-02-27 16:10:45 -0800498 if (mSelectionMode) {
Wenyi Wangd348a902016-08-14 12:56:06 -0700499 final int cabStatusBarColor = ContextCompat.getColor(
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700500 mActivity, R.color.contextual_selection_bar_status_bar_color);
501 runStatusBarAnimation(/* colorTo */ cabStatusBarColor);
Wenyi Wang8e73c362016-06-23 18:03:53 -0700502 } else {
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700503 if (shouldAnimate) {
Wenyi Wang92d7e512016-10-03 14:57:19 -0700504 runStatusBarAnimation(/* colorTo */
505 MaterialColorMapUtils.getStatusBarColor(mActivity));
506 } else if (mActivity instanceof ContactsDrawerActivity) {
507 ((ContactsDrawerActivity) mActivity).updateStatusBarBackground();
Wenyi Wang11ff39a2016-08-07 15:46:03 -0700508 }
509 }
510 }
511
512 private void runStatusBarAnimation(int colorTo) {
513 final Window window = mActivity.getWindow();
514 if (window.getStatusBarColor() != colorTo) {
515 // Cancel running animation.
516 if (mStatusBarAnimator != null && mStatusBarAnimator.isRunning()) {
517 mStatusBarAnimator.cancel();
518 }
519 final int from = window.getStatusBarColor();
520 // Set up mStatusBarAnimator and run animation.
521 mStatusBarAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), from, colorTo);
522 mStatusBarAnimator.addUpdateListener(
523 new ValueAnimator.AnimatorUpdateListener() {
524 @Override
525 public void onAnimationUpdate(ValueAnimator animator) {
526 window.setStatusBarColor((Integer) animator.getAnimatedValue());
527 }
528 });
529 mStatusBarAnimator.setDuration(mActionBarAnimationDuration);
530 mStatusBarAnimator.setStartDelay(0);
531 mStatusBarAnimator.start();
Brian Attwell20510ec2015-02-27 16:10:45 -0800532 }
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700533 }
534
Brian Attwell3c763b32014-07-21 14:25:28 -0700535 private void addSearchContainer() {
536 mToolbar.removeView(mSearchContainer);
537 mToolbar.addView(mSearchContainer);
Brian Attwell865847b2015-04-20 14:40:17 -0700538 mSearchContainer.setAlpha(1);
Brian Attwell3c763b32014-07-21 14:25:28 -0700539 }
540
Brian Attwell20510ec2015-02-27 16:10:45 -0800541 private void addSelectionContainer() {
542 mToolBarFrame.removeView(mSelectionContainer);
543 mToolBarFrame.addView(mSelectionContainer, 0);
Brian Attwell865847b2015-04-20 14:40:17 -0700544 mSelectionContainer.setAlpha(1);
Brian Attwell20510ec2015-02-27 16:10:45 -0800545 }
546
547 private void updateDisplayOptions(boolean isSearchModeChanging) {
Brian Attwell865847b2015-04-20 14:40:17 -0700548 if (mSearchMode && !mSelectionMode) {
Makoto Onuki52673672011-07-25 12:39:59 -0700549 setFocusOnSearchView();
Katherine Kuand8a61b72011-07-27 13:57:13 -0700550 // Since we have the {@link SearchView} in a custom action bar, we must manually handle
Katherine Kuan3c877e32012-01-03 17:27:36 -0800551 // expanding the {@link SearchView} when a search is initiated. Note that a side effect
552 // of this method is that the {@link SearchView} query text is set to empty string.
Brian Attwell20510ec2015-02-27 16:10:45 -0800553 if (isSearchModeChanging) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700554 final CharSequence queryText = mSearchView.getText();
Yorke Lee8d5eec32013-01-18 11:07:48 -0800555 if (!TextUtils.isEmpty(queryText)) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700556 mSearchView.setText(queryText);
Yorke Lee8d5eec32013-01-18 11:07:48 -0800557 }
Daniel Lehmannb0d82d22012-05-07 19:00:06 -0700558 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800559 }
560 if (mListener != null) {
561 if (mSearchMode) {
Katherine Kuanb5760b92011-06-01 16:19:40 -0700562 mListener.onAction(Action.START_SEARCH_MODE);
Dmitri Plotnikovc9916d32010-10-13 11:42:09 -0700563 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800564 if (mSelectionMode) {
565 mListener.onAction(Action.START_SELECTION_MODE);
566 }
567 if (!mSearchMode && !mSelectionMode) {
568 mListener.onAction(Action.STOP_SEARCH_AND_SELECTION_MODE);
Dmitri Plotnikovbee8d3b2010-10-12 20:57:46 -0700569 }
Dmitri Plotnikov88a405f2010-09-27 18:50:57 -0700570 }
Brian Attwell3c763b32014-07-21 14:25:28 -0700571 updateDisplayOptionsInner();
Dmitri Plotnikov1a59cff2010-09-18 12:50:45 -0700572 }
573
574 @Override
Dmitri Plotnikov1a59cff2010-09-18 12:50:45 -0700575 public boolean onClose() {
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700576 setSearchMode(false);
577 return false;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700578 }
579
580 public void onSaveInstanceState(Bundle outState) {
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700581 outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
Brian Attwell20510ec2015-02-27 16:10:45 -0800582 outState.putBoolean(EXTRA_KEY_SELECTED_MODE, mSelectionMode);
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700583 outState.putString(EXTRA_KEY_QUERY, mQueryString);
Dmitri Plotnikovfa49a662011-01-13 11:04:56 -0800584 }
585
Daniel Lehmann09d66a32012-04-13 19:14:39 -0700586 public void setFocusOnSearchView() {
Makoto Onuki4ba903c2011-07-07 16:55:17 -0700587 mSearchView.requestFocus();
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700588 showInputMethod(mSearchView); // Workaround for the "IME not popping up" issue.
589 }
590
591 private void showInputMethod(View view) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800592 final InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700593 Context.INPUT_METHOD_SERVICE);
594 if (imm != null) {
595 imm.showSoftInput(view, 0);
596 }
Makoto Onuki4ba903c2011-07-07 16:55:17 -0700597 }
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700598}