blob: 6a5c1cb2a19c18f516f742ba514fb64c7ac8e30f [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts.activities;
import android.animation.ValueAnimator;
import android.app.ActionBar;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import android.widget.SearchView.OnCloseListener;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toolbar;
import com.android.contacts.R;
import com.android.contacts.activities.ActionBarAdapter.Listener.Action;
import com.android.contacts.list.ContactsRequest;
/**
* Adapter for the action bar at the top of the Contacts activity.
*/
public class ActionBarAdapter implements OnCloseListener {
public interface Listener {
public abstract class Action {
public static final int CHANGE_SEARCH_QUERY = 0;
public static final int START_SEARCH_MODE = 1;
public static final int STOP_SEARCH_MODE = 2;
}
void onAction(int action);
/**
* Called when the user selects a tab. The new tab can be obtained using
* {@link #getCurrentTab}.
*/
void onSelectedTabChanged();
void onUpButtonPressed();
}
private static final String EXTRA_KEY_SEARCH_MODE = "navBar.searchMode";
private static final String EXTRA_KEY_QUERY = "navBar.query";
private static final String EXTRA_KEY_SELECTED_TAB = "navBar.selectedTab";
private static final String PERSISTENT_LAST_TAB = "actionBarAdapter.lastTab";
private boolean mSearchMode;
private String mQueryString;
private EditText mSearchView;
/** The view that represents tabs when we are in portrait mode **/
private View mPortraitTabs;
/** The view that represents tabs when we are in landscape mode **/
private View mLandscapeTabs;
private View mSearchContainer;
private int mMaxPortraitTabHeight;
private int mMaxToolbarContentInsetStart;
private final Context mContext;
private final SharedPreferences mPrefs;
private Listener mListener;
private final ActionBar mActionBar;
private final Toolbar mToolbar;
private boolean mShowHomeIcon;
public interface TabState {
public static int FAVORITES = 0;
public static int ALL = 1;
public static int COUNT = 2;
public static int DEFAULT = ALL;
}
private int mCurrentTab = TabState.DEFAULT;
public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar,
View portraitTabs, View landscapeTabs, Toolbar toolbar) {
mContext = context;
mListener = listener;
mActionBar = actionBar;
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mPortraitTabs = portraitTabs;
mLandscapeTabs = landscapeTabs;
mToolbar = toolbar;
mMaxToolbarContentInsetStart = mToolbar.getContentInsetStart();
mShowHomeIcon = mContext.getResources().getBoolean(R.bool.show_home_icon);
setupSearchView();
setupTabs(context);
}
private void setupTabs(Context context) {
final TypedArray attributeArray = context.obtainStyledAttributes(
new int[]{android.R.attr.actionBarSize});
mMaxPortraitTabHeight = attributeArray.getDimensionPixelSize(0, 0);
// Hide tabs initially
setPortraitTabHeight(0);
}
private void setupSearchView() {
final LayoutInflater inflater = (LayoutInflater) mToolbar.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mSearchContainer = inflater.inflate(R.layout.search_bar_expanded, mToolbar,
/* attachToRoot = */ false);
mSearchContainer.setVisibility(View.VISIBLE);
mToolbar.addView(mSearchContainer);
mSearchContainer.setBackgroundColor(mContext.getResources().getColor(
R.color.searchbox_background_color));
mSearchView = (EditText) mSearchContainer.findViewById(R.id.search_view);
mSearchView.setHint(mContext.getString(R.string.hint_findContacts));
mSearchView.addTextChangedListener(new SearchTextWatcher());
mSearchContainer.findViewById(R.id.search_close_button).setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
mSearchView.setText(null);
}
});
mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onUpButtonPressed();
}
}
});
}
public void initialize(Bundle savedState, ContactsRequest request) {
if (savedState == null) {
mSearchMode = request.isSearchMode();
mQueryString = request.getQueryString();
mCurrentTab = loadLastTabPreference();
} else {
mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
mQueryString = savedState.getString(EXTRA_KEY_QUERY);
// Just set to the field here. The listener will be notified by update().
mCurrentTab = savedState.getInt(EXTRA_KEY_SELECTED_TAB);
}
if (mCurrentTab >= TabState.COUNT || mCurrentTab < 0) {
// Invalid tab index was saved (b/12938207). Restore the default.
mCurrentTab = TabState.DEFAULT;
}
// Show tabs or the expanded {@link SearchView}, depending on whether or not we are in
// search mode.
update(true /* skipAnimation */);
// Expanding the {@link SearchView} clears the query, so set the query from the
// {@link ContactsRequest} after it has been expanded, if applicable.
if (mSearchMode && !TextUtils.isEmpty(mQueryString)) {
setQueryString(mQueryString);
}
}
public void setListener(Listener listener) {
mListener = listener;
}
private class SearchTextWatcher implements TextWatcher {
@Override
public void onTextChanged(CharSequence queryString, int start, int before, int count) {
if (queryString.equals(mQueryString)) {
return;
}
mQueryString = queryString.toString();
if (!mSearchMode) {
if (!TextUtils.isEmpty(queryString)) {
setSearchMode(true);
}
} else if (mListener != null) {
mListener.onAction(Action.CHANGE_SEARCH_QUERY);
}
}
@Override
public void afterTextChanged(Editable s) {}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
}
/**
* Save the current tab selection, and notify the listener.
*/
public void setCurrentTab(int tab) {
setCurrentTab(tab, true);
}
/**
* Save the current tab selection.
*/
public void setCurrentTab(int tab, boolean notifyListener) {
if (tab == mCurrentTab) {
return;
}
mCurrentTab = tab;
if (notifyListener && mListener != null) mListener.onSelectedTabChanged();
saveLastTabPreference(mCurrentTab);
}
public int getCurrentTab() {
return mCurrentTab;
}
/**
* @return Whether in search mode, i.e. if the search view is visible/expanded.
*
* Note even if the action bar is in search mode, if the query is empty, the search fragment
* will not be in search mode.
*/
public boolean isSearchMode() {
return mSearchMode;
}
public void setSearchMode(boolean flag) {
if (mSearchMode != flag) {
mSearchMode = flag;
update(false /* skipAnimation */);
if (mSearchView == null) {
return;
}
if (mSearchMode) {
setFocusOnSearchView();
} else {
mSearchView.setText(null);
}
} else if (flag) {
// Everything is already set up. Still make sure the keyboard is up
if (mSearchView != null) setFocusOnSearchView();
}
}
public String getQueryString() {
return mSearchMode ? mQueryString : null;
}
public void setQueryString(String query) {
mQueryString = query;
if (mSearchView != null) {
mSearchView.setText(query);
}
}
/** @return true if the "UP" icon is showing. */
public boolean isUpShowing() {
return mSearchMode; // Only shown on the search mode.
}
private void updateDisplayOptionsInner() {
// All the flags we may change in this method.
final int MASK = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME
| ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_CUSTOM;
// The current flags set to the action bar. (only the ones that we may change here)
final int current = mActionBar.getDisplayOptions() & MASK;
// Build the new flags...
int newFlags = 0;
if (mShowHomeIcon && !mSearchMode) {
newFlags |= ActionBar.DISPLAY_SHOW_HOME;
}
if (mSearchMode) {
newFlags |= ActionBar.DISPLAY_SHOW_CUSTOM;
mToolbar.setContentInsetsRelative(0, mToolbar.getContentInsetEnd());
} else {
newFlags |= ActionBar.DISPLAY_SHOW_TITLE;
mToolbar.setContentInsetsRelative(mMaxToolbarContentInsetStart,
mToolbar.getContentInsetEnd());
}
if (current != newFlags) {
// Pass the mask here to preserve other flags that we're not interested here.
mActionBar.setDisplayOptions(newFlags, MASK);
}
}
private void update(boolean skipAnimation) {
final boolean isIconifiedChanging
= (mSearchContainer.getParent() == null) == mSearchMode;
if (isIconifiedChanging && !skipAnimation) {
mToolbar.removeView(mLandscapeTabs);
if (mSearchMode) {
addSearchContainer();
mSearchContainer.setAlpha(0);
mSearchContainer.animate().alpha(1);
animateTabHeightChange(mMaxPortraitTabHeight, 0);
updateDisplayOptions(isIconifiedChanging);
} else {
mSearchContainer.setAlpha(1);
animateTabHeightChange(0, mMaxPortraitTabHeight);
mSearchContainer.animate().alpha(0).withEndAction(new Runnable() {
@Override
public void run() {
updateDisplayOptionsInner();
updateDisplayOptions(isIconifiedChanging);
addLandscapeViewPagerTabs();
mToolbar.removeView(mSearchContainer);
}
});
}
return;
}
if (isIconifiedChanging && skipAnimation) {
mToolbar.removeView(mLandscapeTabs);
if (mSearchMode) {
setPortraitTabHeight(0);
addSearchContainer();
} else {
setPortraitTabHeight(mMaxPortraitTabHeight);
mToolbar.removeView(mSearchContainer);
addLandscapeViewPagerTabs();
}
}
updateDisplayOptions(isIconifiedChanging);
}
private void addLandscapeViewPagerTabs() {
if (mLandscapeTabs != null) {
mToolbar.removeView(mLandscapeTabs);
mToolbar.addView(mLandscapeTabs);
}
}
private void addSearchContainer() {
mToolbar.removeView(mSearchContainer);
mToolbar.addView(mSearchContainer);
}
private void updateDisplayOptions(boolean isIconifiedChanging) {
if (mSearchMode) {
setFocusOnSearchView();
// Since we have the {@link SearchView} in a custom action bar, we must manually handle
// expanding the {@link SearchView} when a search is initiated. Note that a side effect
// of this method is that the {@link SearchView} query text is set to empty string.
if (isIconifiedChanging) {
final CharSequence queryText = mSearchView.getText();
if (!TextUtils.isEmpty(queryText)) {
mSearchView.setText(queryText);
}
}
if (mListener != null) {
mListener.onAction(Action.START_SEARCH_MODE);
}
} else {
if (mListener != null) {
mListener.onAction(Action.STOP_SEARCH_MODE);
mListener.onSelectedTabChanged();
}
}
updateDisplayOptionsInner();
}
@Override
public boolean onClose() {
setSearchMode(false);
return false;
}
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode);
outState.putString(EXTRA_KEY_QUERY, mQueryString);
outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab);
}
/**
* Clears the focus from the {@link SearchView} if we are in search mode.
* This will suppress the IME if it is visible.
*/
public void clearFocusOnSearchView() {
if (isSearchMode()) {
if (mSearchView != null) {
mSearchView.clearFocus();
}
}
}
public void setFocusOnSearchView() {
mSearchView.requestFocus();
showInputMethod(mSearchView); // Workaround for the "IME not popping up" issue.
}
private void showInputMethod(View view) {
final InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(view, 0);
}
}
private void saveLastTabPreference(int tab) {
mPrefs.edit().putInt(PERSISTENT_LAST_TAB, tab).apply();
}
private int loadLastTabPreference() {
try {
return mPrefs.getInt(PERSISTENT_LAST_TAB, TabState.DEFAULT);
} catch (IllegalArgumentException e) {
// Preference is corrupt?
return TabState.DEFAULT;
}
}
private void animateTabHeightChange(int start, int end) {
if (mPortraitTabs == null) {
return;
}
final ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
setPortraitTabHeight(value);
}
});
animator.setDuration(100).start();
}
private void setPortraitTabHeight(int height) {
if (mPortraitTabs == null) {
return;
}
ViewGroup.LayoutParams layoutParams = mPortraitTabs.getLayoutParams();
layoutParams.height = height;
mPortraitTabs.setLayoutParams(layoutParams);
}
}