blob: ad70d92107b569ffbd82272da4e44e45bbd7482d [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
Brian Attwell3c763b32014-07-21 14:25:28 -070019import android.animation.ValueAnimator;
Brian Attwell20510ec2015-02-27 16:10:45 -080020import android.app.Activity;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070021import android.content.Context;
Makoto Onuki35d4aec2011-07-20 12:35:44 -070022import android.content.SharedPreferences;
Brian Attwell3c763b32014-07-21 14:25:28 -070023import android.content.res.TypedArray;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070024import android.os.Bundle;
Makoto Onuki35d4aec2011-07-20 12:35:44 -070025import android.preference.PreferenceManager;
Wenyi Wang23d9b6e2015-11-19 15:09:06 -080026import android.support.v4.content.ContextCompat;
Wenyi Wang3c3b6f12016-04-02 13:58:43 -070027import android.support.v7.app.ActionBar;
28import android.support.v7.widget.Toolbar;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070029import android.text.Editable;
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070030import android.text.TextUtils;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070031import android.text.TextWatcher;
Brian Attwell20510ec2015-02-27 16:10:45 -080032import android.view.Gravity;
Brian Attwell3c763b32014-07-21 14:25:28 -070033import android.view.LayoutInflater;
Katherine Kuan1ee9df62011-06-11 16:54:01 -070034import android.view.View;
Brian Attwell3c763b32014-07-21 14:25:28 -070035import android.view.ViewGroup;
Katherine Kuand8a61b72011-07-27 13:57:13 -070036import android.view.inputmethod.InputMethodManager;
Brian Attwell20510ec2015-02-27 16:10:45 -080037import android.widget.FrameLayout;
38import android.widget.LinearLayout.LayoutParams;
Dmitri Plotnikov1a59cff2010-09-18 12:50:45 -070039import android.widget.SearchView.OnCloseListener;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070040import android.view.View.OnClickListener;
41import android.widget.EditText;
Brian Attwell20510ec2015-02-27 16:10:45 -080042import android.widget.TextView;
Dmitri Plotnikov92a6fdf2010-07-09 13:19:33 -070043
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070044import com.android.contacts.R;
45import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
Wenyi Wang4c3d3e22015-12-17 14:30:02 -080046import com.android.contacts.common.compat.CompatUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070047import com.android.contacts.list.ContactsRequest;
48
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070049/**
Dmitri Plotnikovfcfdffb2010-07-15 18:00:55 -070050 * Adapter for the action bar at the top of the Contacts activity.
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070051 */
Brian Attwell6adbf1e2014-05-30 16:44:51 -070052public class ActionBarAdapter implements OnCloseListener {
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070053
54 public interface Listener {
Daniel Lehmann311882a2012-04-27 22:31:38 -070055 public abstract class Action {
56 public static final int CHANGE_SEARCH_QUERY = 0;
57 public static final int START_SEARCH_MODE = 1;
Brian Attwell20510ec2015-02-27 16:10:45 -080058 public static final int START_SELECTION_MODE = 2;
59 public static final int STOP_SEARCH_AND_SELECTION_MODE = 3;
Brian Attwell865847b2015-04-20 14:40:17 -070060 public static final int BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE = 4;
Katherine Kuanb5760b92011-06-01 16:19:40 -070061 }
62
Daniel Lehmann311882a2012-04-27 22:31:38 -070063 void onAction(int action);
Makoto Onuki4d788fc2011-07-12 10:00:10 -070064
65 /**
66 * Called when the user selects a tab. The new tab can be obtained using
67 * {@link #getCurrentTab}.
68 */
69 void onSelectedTabChanged();
Brian Attwell6adbf1e2014-05-30 16:44:51 -070070
71 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";
Isaac Katzenelson59dc2752011-07-06 17:04:42 -070076 private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
Brian Attwell20510ec2015-02-27 16:10:45 -080077 private static final String EXTRA_KEY_SELECTED_MODE = "navBar.selectionMode";
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070078
Makoto Onuki35d4aec2011-07-20 12:35:44 -070079 private static final String PERSISTENT_LAST_TAB = "actionBarAdapter.lastTab";
80
Brian Attwell20510ec2015-02-27 16:10:45 -080081 private boolean mSelectionMode;
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070082 private boolean mSearchMode;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070083 private String mQueryString;
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -070084
Brian Attwell6adbf1e2014-05-30 16:44:51 -070085 private EditText mSearchView;
Andrew Leead6e0ee2015-05-05 17:47:42 -070086 private View mClearSearchView;
Brian Attwell3c763b32014-07-21 14:25:28 -070087 /** The view that represents tabs when we are in portrait mode **/
88 private View mPortraitTabs;
89 /** The view that represents tabs when we are in landscape mode **/
90 private View mLandscapeTabs;
Brian Attwell6adbf1e2014-05-30 16:44:51 -070091 private View mSearchContainer;
Brian Attwell20510ec2015-02-27 16:10:45 -080092 private View mSelectionContainer;
Dmitri Plotnikov92a6fdf2010-07-09 13:19:33 -070093
Brian Attwell3c763b32014-07-21 14:25:28 -070094 private int mMaxPortraitTabHeight;
95 private int mMaxToolbarContentInsetStart;
96
Brian Attwell20510ec2015-02-27 16:10:45 -080097 private final Activity mActivity;
Makoto Onuki35d4aec2011-07-20 12:35:44 -070098 private final SharedPreferences mPrefs;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -070099
100 private Listener mListener;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700101
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700102 private final ActionBar mActionBar;
Brian Attwell3c763b32014-07-21 14:25:28 -0700103 private final Toolbar mToolbar;
Brian Attwell20510ec2015-02-27 16:10:45 -0800104 /**
105 * Frame that contains the toolbar and draws the toolbar's background color. This is useful
106 * for placing things behind the toolbar.
107 */
108 private final FrameLayout mToolBarFrame;
Dmitri Plotnikov8b826462010-11-17 14:52:24 -0800109
Makoto Onukie0c66af2011-07-13 14:24:03 -0700110 private boolean mShowHomeIcon;
111
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800112 public interface TabState {
Paul Sliwowski3b6a6f62013-09-05 01:20:12 -0700113 public static int FAVORITES = 0;
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800114 public static int ALL = 1;
Katherine Kuan1ee9df62011-06-11 16:54:01 -0700115
Paul Soulos5f057fa2014-05-14 15:45:50 -0700116 public static int COUNT = 2;
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800117 public static int DEFAULT = ALL;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700118 }
119
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800120 private int mCurrentTab = TabState.DEFAULT;
Katherine Kuanb5760b92011-06-01 16:19:40 -0700121
Brian Attwell20510ec2015-02-27 16:10:45 -0800122 public ActionBarAdapter(Activity activity, Listener listener, ActionBar actionBar,
Brian Attwell3c763b32014-07-21 14:25:28 -0700123 View portraitTabs, View landscapeTabs, Toolbar toolbar) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800124 mActivity = activity;
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700125 mListener = listener;
126 mActionBar = actionBar;
Brian Attwell20510ec2015-02-27 16:10:45 -0800127 mPrefs = PreferenceManager.getDefaultSharedPreferences(mActivity);
Brian Attwell3c763b32014-07-21 14:25:28 -0700128 mPortraitTabs = portraitTabs;
129 mLandscapeTabs = landscapeTabs;
130 mToolbar = toolbar;
Brian Attwell20510ec2015-02-27 16:10:45 -0800131 mToolBarFrame = (FrameLayout) mToolbar.getParent();
Brian Attwell3c763b32014-07-21 14:25:28 -0700132 mMaxToolbarContentInsetStart = mToolbar.getContentInsetStart();
Brian Attwell20510ec2015-02-27 16:10:45 -0800133 mShowHomeIcon = mActivity.getResources().getBoolean(R.bool.show_home_icon);
Makoto Onukie0c66af2011-07-13 14:24:03 -0700134
Brian Attwell20510ec2015-02-27 16:10:45 -0800135 setupSearchAndSelectionViews();
136 setupTabs(mActivity);
Maurice Chu7f5220e2012-05-14 10:00:28 -0700137 }
138
Brian Attwell3c763b32014-07-21 14:25:28 -0700139 private void setupTabs(Context context) {
140 final TypedArray attributeArray = context.obtainStyledAttributes(
141 new int[]{android.R.attr.actionBarSize});
142 mMaxPortraitTabHeight = attributeArray.getDimensionPixelSize(0, 0);
143 // Hide tabs initially
144 setPortraitTabHeight(0);
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700145 }
Andrew Leead6e0ee2015-05-05 17:47:42 -0700146
Brian Attwell20510ec2015-02-27 16:10:45 -0800147 private void setupSearchAndSelectionViews() {
Brian Attwell3c763b32014-07-21 14:25:28 -0700148 final LayoutInflater inflater = (LayoutInflater) mToolbar.getContext().getSystemService(
149 Context.LAYOUT_INFLATER_SERVICE);
Brian Attwell20510ec2015-02-27 16:10:45 -0800150
151 // Setup search bar
Brian Attwell3c763b32014-07-21 14:25:28 -0700152 mSearchContainer = inflater.inflate(R.layout.search_bar_expanded, mToolbar,
153 /* attachToRoot = */ false);
154 mSearchContainer.setVisibility(View.VISIBLE);
155 mToolbar.addView(mSearchContainer);
Brian Attwell20510ec2015-02-27 16:10:45 -0800156 mSearchContainer.setBackgroundColor(mActivity.getResources().getColor(
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700157 R.color.searchbox_background_color));
158 mSearchView = (EditText) mSearchContainer.findViewById(R.id.search_view);
Brian Attwell20510ec2015-02-27 16:10:45 -0800159 mSearchView.setHint(mActivity.getString(R.string.hint_findContacts));
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700160 mSearchView.addTextChangedListener(new SearchTextWatcher());
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700161 mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(
162 new OnClickListener() {
163 @Override
164 public void onClick(View v) {
165 if (mListener != null) {
166 mListener.onUpButtonPressed();
167 }
168 }
169 });
Brian Attwell20510ec2015-02-27 16:10:45 -0800170
Andrew Leead6e0ee2015-05-05 17:47:42 -0700171 mClearSearchView = mSearchContainer.findViewById(R.id.search_close_button);
172 mClearSearchView.setOnClickListener(
173 new OnClickListener() {
174 @Override
175 public void onClick(View v) {
176 setQueryString(null);
177 }
178 });
179
Brian Attwell20510ec2015-02-27 16:10:45 -0800180 // Setup selection bar
181 mSelectionContainer = inflater.inflate(R.layout.selection_bar, mToolbar,
182 /* attachToRoot = */ false);
183 // Insert the selection container into mToolBarFrame behind the Toolbar, so that
184 // the Toolbar's MenuItems can appear on top of the selection container.
185 mToolBarFrame.addView(mSelectionContainer, 0);
186 mSelectionContainer.findViewById(R.id.selection_close).setOnClickListener(
187 new OnClickListener() {
188 @Override
189 public void onClick(View v) {
190 if (mListener != null) {
191 mListener.onUpButtonPressed();
192 }
193 }
194 });
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700195 }
196
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700197 public void initialize(Bundle savedState, ContactsRequest request) {
198 if (savedState == null) {
199 mSearchMode = request.isSearchMode();
200 mQueryString = request.getQueryString();
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700201 mCurrentTab = loadLastTabPreference();
Brian Attwelld2962a32015-03-02 14:48:50 -0800202 mSelectionMode = false;
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700203 } else {
204 mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
Brian Attwell20510ec2015-02-27 16:10:45 -0800205 mSelectionMode = savedState.getBoolean(EXTRA_KEY_SELECTED_MODE);
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700206 mQueryString = savedState.getString(EXTRA_KEY_QUERY);
Wenyi Wang9eb87f42016-01-23 22:40:21 +0000207
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700208 // Just set to the field here. The listener will be notified by update().
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800209 mCurrentTab = savedState.getInt(EXTRA_KEY_SELECTED_TAB);
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700210 }
Brian Attwell26dc5592014-02-07 17:10:03 -0800211 if (mCurrentTab >= TabState.COUNT || mCurrentTab < 0) {
212 // Invalid tab index was saved (b/12938207). Restore the default.
213 mCurrentTab = TabState.DEFAULT;
214 }
Katherine Kuan3c877e32012-01-03 17:27:36 -0800215 // Show tabs or the expanded {@link SearchView}, depending on whether or not we are in
216 // search mode.
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700217 update(true /* skipAnimation */);
Katherine Kuan3c877e32012-01-03 17:27:36 -0800218 // Expanding the {@link SearchView} clears the query, so set the query from the
219 // {@link ContactsRequest} after it has been expanded, if applicable.
220 if (mSearchMode && !TextUtils.isEmpty(mQueryString)) {
221 setQueryString(mQueryString);
222 }
Dmitri Plotnikov8b826462010-11-17 14:52:24 -0800223 }
224
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700225 public void setListener(Listener listener) {
226 mListener = listener;
227 }
228
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700229 private class SearchTextWatcher implements TextWatcher {
230
231 @Override
232 public void onTextChanged(CharSequence queryString, int start, int before, int count) {
233 if (queryString.equals(mQueryString)) {
234 return;
235 }
236 mQueryString = queryString.toString();
237 if (!mSearchMode) {
238 if (!TextUtils.isEmpty(queryString)) {
239 setSearchMode(true);
240 }
241 } else if (mListener != null) {
242 mListener.onAction(Action.CHANGE_SEARCH_QUERY);
243 }
Andrew Leead6e0ee2015-05-05 17:47:42 -0700244 mClearSearchView.setVisibility(
245 TextUtils.isEmpty(queryString) ? View.GONE : View.VISIBLE);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700246 }
247
248 @Override
249 public void afterTextChanged(Editable s) {}
250
251 @Override
252 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
253 }
254
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700255 /**
Brian Attwell3c763b32014-07-21 14:25:28 -0700256 * Save the current tab selection, and notify the listener.
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700257 */
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800258 public void setCurrentTab(int tab) {
Makoto Onuki250ce432011-07-12 18:26:45 -0700259 setCurrentTab(tab, true);
260 }
261
262 /**
Brian Attwell3c763b32014-07-21 14:25:28 -0700263 * Save the current tab selection.
Makoto Onuki250ce432011-07-12 18:26:45 -0700264 */
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800265 public void setCurrentTab(int tab, boolean notifyListener) {
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700266 if (tab == mCurrentTab) {
267 return;
268 }
269 mCurrentTab = tab;
270
Makoto Onuki250ce432011-07-12 18:26:45 -0700271 if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700272 saveLastTabPreference(mCurrentTab);
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700273 }
274
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800275 public int getCurrentTab() {
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700276 return mCurrentTab;
277 }
278
Makoto Onuki1db00f62011-10-19 12:40:12 -0700279 /**
280 * @return Whether in search mode, i.e. if the search view is visible/expanded.
281 *
282 * Note even if the action bar is in search mode, if the query is empty, the search fragment
283 * will not be in search mode.
284 */
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700285 public boolean isSearchMode() {
286 return mSearchMode;
287 }
288
Brian Attwell20510ec2015-02-27 16:10:45 -0800289 /**
290 * @return Whether in selection mode, i.e. if the selection view is visible/expanded.
291 */
292 public boolean isSelectionMode() {
293 return mSelectionMode;
294 }
295
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700296 public void setSearchMode(boolean flag) {
297 if (mSearchMode != flag) {
298 mSearchMode = flag;
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700299 update(false /* skipAnimation */);
Katherine Kuanf3f933a2011-06-10 11:34:54 -0700300 if (mSearchView == null) {
301 return;
302 }
Dmitri Plotnikov0b51a8d2010-10-21 21:22:52 -0700303 if (mSearchMode) {
Brian Attwell87901e02014-12-01 19:11:31 -0800304 mSearchView.setEnabled(true);
Makoto Onuki4ba903c2011-07-07 16:55:17 -0700305 setFocusOnSearchView();
Brian Attwell87901e02014-12-01 19:11:31 -0800306 } else {
307 // Disable search view, so that it doesn't keep the IME visible.
308 mSearchView.setEnabled(false);
Dmitri Plotnikov0b51a8d2010-10-21 21:22:52 -0700309 }
Brian Attwellb3944dc2014-11-18 20:53:59 -0800310 setQueryString(null);
Daniel Lehmann311882a2012-04-27 22:31:38 -0700311 } else if (flag) {
312 // Everything is already set up. Still make sure the keyboard is up
313 if (mSearchView != null) setFocusOnSearchView();
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700314 }
315 }
316
Brian Attwell20510ec2015-02-27 16:10:45 -0800317 public void setSelectionMode(boolean flag) {
318 if (mSelectionMode != flag) {
319 mSelectionMode = flag;
320 update(false /* skipAnimation */);
321 }
322 }
323
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700324 public String getQueryString() {
Makoto Onuki1db00f62011-10-19 12:40:12 -0700325 return mSearchMode ? mQueryString : null;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700326 }
327
328 public void setQueryString(String query) {
329 mQueryString = query;
Katherine Kuanf3f933a2011-06-10 11:34:54 -0700330 if (mSearchView != null) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700331 mSearchView.setText(query);
Brian Attwellb3944dc2014-11-18 20:53:59 -0800332 // When programmatically entering text into the search view, the most reasonable
333 // place for the cursor is after all the text.
334 mSearchView.setSelection(mSearchView.getText() == null ?
335 0 : mSearchView.getText().length());
Katherine Kuanf3f933a2011-06-10 11:34:54 -0700336 }
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700337 }
338
Makoto Onukie0c66af2011-07-13 14:24:03 -0700339 /** @return true if the "UP" icon is showing. */
340 public boolean isUpShowing() {
341 return mSearchMode; // Only shown on the search mode.
342 }
343
Brian Attwell3c763b32014-07-21 14:25:28 -0700344 private void updateDisplayOptionsInner() {
Makoto Onukie0c66af2011-07-13 14:24:03 -0700345 // All the flags we may change in this method.
346 final int MASK = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME
Brian Attwell20510ec2015-02-27 16:10:45 -0800347 | ActionBar.DISPLAY_HOME_AS_UP;
Makoto Onukie0c66af2011-07-13 14:24:03 -0700348
349 // The current flags set to the action bar. (only the ones that we may change here)
350 final int current = mActionBar.getDisplayOptions() & MASK;
351
Brian Attwell20510ec2015-02-27 16:10:45 -0800352 final boolean isSearchOrSelectionMode = mSearchMode || mSelectionMode;
353
Makoto Onukie0c66af2011-07-13 14:24:03 -0700354 // Build the new flags...
355 int newFlags = 0;
Brian Attwell20510ec2015-02-27 16:10:45 -0800356 if (mShowHomeIcon && !isSearchOrSelectionMode) {
Makoto Onukie0c66af2011-07-13 14:24:03 -0700357 newFlags |= ActionBar.DISPLAY_SHOW_HOME;
358 }
Brian Attwell865847b2015-04-20 14:40:17 -0700359 if (mSearchMode && !mSelectionMode) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800360 // The search container is placed inside the toolbar. So we need to disable the
361 // Toolbar's content inset in order to allow the search container to be the width of
362 // the window.
Brian Attwell3c763b32014-07-21 14:25:28 -0700363 mToolbar.setContentInsetsRelative(0, mToolbar.getContentInsetEnd());
Brian Attwell20510ec2015-02-27 16:10:45 -0800364 }
365 if (!isSearchOrSelectionMode) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700366 newFlags |= ActionBar.DISPLAY_SHOW_TITLE;
Brian Attwell3c763b32014-07-21 14:25:28 -0700367 mToolbar.setContentInsetsRelative(mMaxToolbarContentInsetStart,
368 mToolbar.getContentInsetEnd());
Makoto Onukie0c66af2011-07-13 14:24:03 -0700369 }
Brian Attwellb0d3c762014-05-22 03:44:22 +0000370
Brian Attwell20510ec2015-02-27 16:10:45 -0800371 if (mSelectionMode) {
372 // Minimize the horizontal width of the Toolbar since the selection container is placed
373 // behind the toolbar and its left hand side needs to be clickable.
374 FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mToolbar.getLayoutParams();
375 params.width = LayoutParams.WRAP_CONTENT;
376 params.gravity = Gravity.END;
377 mToolbar.setLayoutParams(params);
378 } else {
379 FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mToolbar.getLayoutParams();
380 params.width = LayoutParams.MATCH_PARENT;
381 params.gravity = Gravity.END;
382 mToolbar.setLayoutParams(params);
383 }
Brian Attwell3c763b32014-07-21 14:25:28 -0700384
Makoto Onukie0c66af2011-07-13 14:24:03 -0700385 if (current != newFlags) {
386 // Pass the mask here to preserve other flags that we're not interested here.
387 mActionBar.setDisplayOptions(newFlags, MASK);
388 }
389 }
390
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700391 private void update(boolean skipAnimation) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800392 updateStatusBarColor();
393
394 final boolean isSelectionModeChanging
395 = (mSelectionContainer.getParent() == null) == mSelectionMode;
Brian Attwell865847b2015-04-20 14:40:17 -0700396 final boolean isSwitchingFromSearchToSelection =
397 mSearchMode && isSelectionModeChanging || mSearchMode && mSelectionMode;
Brian Attwell20510ec2015-02-27 16:10:45 -0800398 final boolean isSearchModeChanging
Brian Attwell3c763b32014-07-21 14:25:28 -0700399 = (mSearchContainer.getParent() == null) == mSearchMode;
Brian Attwell20510ec2015-02-27 16:10:45 -0800400 final boolean isTabHeightChanging = isSearchModeChanging || isSelectionModeChanging;
401
402 // When skipAnimation=true, it is possible that we will switch from search mode
403 // to selection mode directly. So we need to remove the undesired container in addition
404 // to adding the desired container.
Brian Attwell865847b2015-04-20 14:40:17 -0700405 if (skipAnimation || isSwitchingFromSearchToSelection) {
406 if (isTabHeightChanging || isSwitchingFromSearchToSelection) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800407 mToolbar.removeView(mLandscapeTabs);
Brian Attwell865847b2015-04-20 14:40:17 -0700408 mToolbar.removeView(mSearchContainer);
409 mToolBarFrame.removeView(mSelectionContainer);
410 if (mSelectionMode) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800411 setPortraitTabHeight(0);
Brian Attwell20510ec2015-02-27 16:10:45 -0800412 addSelectionContainer();
Brian Attwell865847b2015-04-20 14:40:17 -0700413 } else if (mSearchMode) {
414 setPortraitTabHeight(0);
415 addSearchContainer();
Brian Attwell20510ec2015-02-27 16:10:45 -0800416 } else {
417 setPortraitTabHeight(mMaxPortraitTabHeight);
Brian Attwell20510ec2015-02-27 16:10:45 -0800418 addLandscapeViewPagerTabs();
419 }
420 updateDisplayOptions(isSearchModeChanging);
421 }
422 return;
423 }
424
425 // Handle a switch to/from selection mode, due to UI interaction.
426 if (isSelectionModeChanging) {
427 mToolbar.removeView(mLandscapeTabs);
428 if (mSelectionMode) {
429 addSelectionContainer();
430 mSelectionContainer.setAlpha(0);
431 mSelectionContainer.animate().alpha(1);
432 animateTabHeightChange(mMaxPortraitTabHeight, 0);
433 updateDisplayOptions(isSearchModeChanging);
434 } else {
Brian Attwell865847b2015-04-20 14:40:17 -0700435 if (mListener != null) {
436 mListener.onAction(Action.BEGIN_STOPPING_SEARCH_AND_SELECTION_MODE);
437 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800438 mSelectionContainer.setAlpha(1);
439 animateTabHeightChange(0, mMaxPortraitTabHeight);
440 mSelectionContainer.animate().alpha(0).withEndAction(new Runnable() {
441 @Override
442 public void run() {
443 updateDisplayOptions(isSearchModeChanging);
444 addLandscapeViewPagerTabs();
445 mToolBarFrame.removeView(mSelectionContainer);
446 }
447 });
448 }
449 }
450
451 // Handle a switch to/from search mode, due to UI interaction.
452 if (isSearchModeChanging) {
Brian Attwell8d71f112014-07-23 18:13:27 -0700453 mToolbar.removeView(mLandscapeTabs);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700454 if (mSearchMode) {
Brian Attwell3c763b32014-07-21 14:25:28 -0700455 addSearchContainer();
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700456 mSearchContainer.setAlpha(0);
457 mSearchContainer.animate().alpha(1);
Brian Attwell3c763b32014-07-21 14:25:28 -0700458 animateTabHeightChange(mMaxPortraitTabHeight, 0);
Brian Attwell20510ec2015-02-27 16:10:45 -0800459 updateDisplayOptions(isSearchModeChanging);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700460 } else {
461 mSearchContainer.setAlpha(1);
Brian Attwell3c763b32014-07-21 14:25:28 -0700462 animateTabHeightChange(0, mMaxPortraitTabHeight);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700463 mSearchContainer.animate().alpha(0).withEndAction(new Runnable() {
464 @Override
465 public void run() {
Brian Attwell20510ec2015-02-27 16:10:45 -0800466 updateDisplayOptions(isSearchModeChanging);
Brian Attwell3c763b32014-07-21 14:25:28 -0700467 addLandscapeViewPagerTabs();
468 mToolbar.removeView(mSearchContainer);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700469 }
470 });
471 }
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700472 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800473 }
474
475 public void setSelectionCount(int selectionCount) {
476 TextView textView = (TextView) mSelectionContainer.findViewById(R.id.selection_count_text);
477 if (selectionCount == 0) {
478 textView.setVisibility(View.GONE);
479 } else {
480 textView.setVisibility(View.VISIBLE);
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700481 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800482 textView.setText(String.valueOf(selectionCount));
483 }
484
485 private void updateStatusBarColor() {
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800486 if (!CompatUtils.isLollipopCompatible()) {
487 return; // we can't change the status bar color prior to Lollipop
488 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800489 if (mSelectionMode) {
Wenyi Wang56dfa582015-11-17 17:42:44 -0800490 final int cabStatusBarColor = mActivity.getResources().getColor(
Brian Attwell20510ec2015-02-27 16:10:45 -0800491 R.color.contextual_selection_bar_status_bar_color);
492 mActivity.getWindow().setStatusBarColor(cabStatusBarColor);
493 } else {
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800494 final int normalStatusBarColor = ContextCompat.getColor(
495 mActivity, R.color.primary_color_dark);
Brian Attwell20510ec2015-02-27 16:10:45 -0800496 mActivity.getWindow().setStatusBarColor(normalStatusBarColor);
497 }
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700498 }
499
Brian Attwell3c763b32014-07-21 14:25:28 -0700500 private void addLandscapeViewPagerTabs() {
501 if (mLandscapeTabs != null) {
502 mToolbar.removeView(mLandscapeTabs);
503 mToolbar.addView(mLandscapeTabs);
504 }
505 }
506
507 private void addSearchContainer() {
508 mToolbar.removeView(mSearchContainer);
509 mToolbar.addView(mSearchContainer);
Brian Attwell865847b2015-04-20 14:40:17 -0700510 mSearchContainer.setAlpha(1);
Brian Attwell3c763b32014-07-21 14:25:28 -0700511 }
512
Brian Attwell20510ec2015-02-27 16:10:45 -0800513 private void addSelectionContainer() {
514 mToolBarFrame.removeView(mSelectionContainer);
515 mToolBarFrame.addView(mSelectionContainer, 0);
Brian Attwell865847b2015-04-20 14:40:17 -0700516 mSelectionContainer.setAlpha(1);
Brian Attwell20510ec2015-02-27 16:10:45 -0800517 }
518
519 private void updateDisplayOptions(boolean isSearchModeChanging) {
Brian Attwell865847b2015-04-20 14:40:17 -0700520 if (mSearchMode && !mSelectionMode) {
Makoto Onuki52673672011-07-25 12:39:59 -0700521 setFocusOnSearchView();
Katherine Kuand8a61b72011-07-27 13:57:13 -0700522 // Since we have the {@link SearchView} in a custom action bar, we must manually handle
Katherine Kuan3c877e32012-01-03 17:27:36 -0800523 // expanding the {@link SearchView} when a search is initiated. Note that a side effect
524 // of this method is that the {@link SearchView} query text is set to empty string.
Brian Attwell20510ec2015-02-27 16:10:45 -0800525 if (isSearchModeChanging) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700526 final CharSequence queryText = mSearchView.getText();
Yorke Lee8d5eec32013-01-18 11:07:48 -0800527 if (!TextUtils.isEmpty(queryText)) {
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700528 mSearchView.setText(queryText);
Yorke Lee8d5eec32013-01-18 11:07:48 -0800529 }
Daniel Lehmannb0d82d22012-05-07 19:00:06 -0700530 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800531 }
532 if (mListener != null) {
533 if (mSearchMode) {
Katherine Kuanb5760b92011-06-01 16:19:40 -0700534 mListener.onAction(Action.START_SEARCH_MODE);
Dmitri Plotnikovc9916d32010-10-13 11:42:09 -0700535 }
Brian Attwell20510ec2015-02-27 16:10:45 -0800536 if (mSelectionMode) {
537 mListener.onAction(Action.START_SELECTION_MODE);
538 }
539 if (!mSearchMode && !mSelectionMode) {
540 mListener.onAction(Action.STOP_SEARCH_AND_SELECTION_MODE);
Makoto Onuki4d788fc2011-07-12 10:00:10 -0700541 mListener.onSelectedTabChanged();
Dmitri Plotnikovbee8d3b2010-10-12 20:57:46 -0700542 }
Dmitri Plotnikov88a405f2010-09-27 18:50:57 -0700543 }
Brian Attwell3c763b32014-07-21 14:25:28 -0700544 updateDisplayOptionsInner();
Dmitri Plotnikov1a59cff2010-09-18 12:50:45 -0700545 }
546
547 @Override
Dmitri Plotnikov1a59cff2010-09-18 12:50:45 -0700548 public boolean onClose() {
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700549 setSearchMode(false);
550 return false;
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700551 }
552
553 public void onSaveInstanceState(Bundle outState) {
Dmitri Plotnikov15ccbb42010-10-05 17:29:07 -0700554 outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
Brian Attwell20510ec2015-02-27 16:10:45 -0800555 outState.putBoolean(EXTRA_KEY_SELECTED_MODE, mSelectionMode);
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700556 outState.putString(EXTRA_KEY_QUERY, mQueryString);
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800557 outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab);
Dmitri Plotnikovfa49a662011-01-13 11:04:56 -0800558 }
559
Daniel Lehmann09d66a32012-04-13 19:14:39 -0700560 public void setFocusOnSearchView() {
Makoto Onuki4ba903c2011-07-07 16:55:17 -0700561 mSearchView.requestFocus();
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700562 showInputMethod(mSearchView); // Workaround for the "IME not popping up" issue.
563 }
564
565 private void showInputMethod(View view) {
Brian Attwell20510ec2015-02-27 16:10:45 -0800566 final InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(
Brian Attwell6adbf1e2014-05-30 16:44:51 -0700567 Context.INPUT_METHOD_SERVICE);
568 if (imm != null) {
569 imm.showSoftInput(view, 0);
570 }
Makoto Onuki4ba903c2011-07-07 16:55:17 -0700571 }
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700572
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800573 private void saveLastTabPreference(int tab) {
574 mPrefs.edit().putInt(PERSISTENT_LAST_TAB, tab).apply();
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700575 }
576
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800577 private int loadLastTabPreference() {
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700578 try {
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800579 return mPrefs.getInt(PERSISTENT_LAST_TAB, TabState.DEFAULT);
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700580 } catch (IllegalArgumentException e) {
581 // Preference is corrupt?
Daniel Lehmann52aa32d2012-03-08 17:29:27 -0800582 return TabState.DEFAULT;
Makoto Onuki35d4aec2011-07-20 12:35:44 -0700583 }
584 }
Brian Attwell3c763b32014-07-21 14:25:28 -0700585
586 private void animateTabHeightChange(int start, int end) {
587 if (mPortraitTabs == null) {
588 return;
589 }
590 final ValueAnimator animator = ValueAnimator.ofInt(start, end);
591 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
592 @Override
593 public void onAnimationUpdate(ValueAnimator valueAnimator) {
594 int value = (Integer) valueAnimator.getAnimatedValue();
595 setPortraitTabHeight(value);
596 }
597 });
598 animator.setDuration(100).start();
599 }
600
601 private void setPortraitTabHeight(int height) {
602 if (mPortraitTabs == null) {
603 return;
604 }
605 ViewGroup.LayoutParams layoutParams = mPortraitTabs.getLayoutParams();
606 layoutParams.height = height;
607 mPortraitTabs.setLayoutParams(layoutParams);
608 }
Dmitri Plotnikov08e1c4e2010-07-02 17:57:51 -0700609}