diff options
author | Danny Baumann <dannybaumann@web.de> | 2015-05-29 09:22:03 +0200 |
---|---|---|
committer | Danny Baumann <dannybaumann@web.de> | 2015-06-10 06:56:41 +0000 |
commit | a3a0c7722ecc9162adc7575542bdce70f9d9c76a (patch) | |
tree | 265819568c006d61669582d3950cc4d9fddc78e1 | |
parent | b9bbfc413236c05feb2381be5c05a554d599b2ef (diff) | |
download | android_packages_apps_Dialer-a3a0c7722ecc9162adc7575542bdce70f9d9c76a.tar.gz android_packages_apps_Dialer-a3a0c7722ecc9162adc7575542bdce70f9d9c76a.tar.bz2 android_packages_apps_Dialer-a3a0c7722ecc9162adc7575542bdce70f9d9c76a.zip |
Fix up call log search UX.
Change-Id: I0dc9556af8ae7328236a0efab0c96cdabc9938fc
-rw-r--r-- | AndroidManifest.xml | 2 | ||||
-rw-r--r-- | res/layout/call_log_activity.xml | 29 | ||||
-rw-r--r-- | res/menu/call_log_options.xml | 3 | ||||
-rw-r--r-- | res/values/styles.xml | 16 | ||||
-rw-r--r-- | src/com/android/dialer/PhoneCallDetailsHelper.java | 43 | ||||
-rwxr-xr-x | src/com/android/dialer/calllog/CallLogActivity.java | 616 | ||||
-rwxr-xr-x | src/com/android/dialer/calllog/CallLogAdapter.java | 2 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/CallLogListItemHelper.java | 5 | ||||
-rw-r--r-- | src/com/android/dialer/calllog/MSimCallLogFragment.java | 14 | ||||
-rw-r--r-- | src/com/android/dialer/callstats/CallStatsFragment.java | 16 | ||||
-rw-r--r-- | tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java | 9 |
11 files changed, 470 insertions, 285 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a10b2c73f..580d567a3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -144,7 +144,7 @@ <activity android:name="com.android.dialer.calllog.CallLogActivity" android:label="@string/call_log_activity_title" - android:theme="@style/DialtactsThemeWithoutActionBarOverlay" + android:theme="@style/DialtactsThemeWithoutActionBar" android:screenOrientation="nosensor" android:icon="@mipmap/ic_launcher_phone"> <intent-filter> diff --git a/res/layout/call_log_activity.xml b/res/layout/call_log_activity.xml index 0109df7d6..67437db7f 100644 --- a/res/layout/call_log_activity.xml +++ b/res/layout/call_log_activity.xml @@ -19,15 +19,30 @@ android:layout_height="match_parent" android:id="@+id/calllog_frame" android:orientation="vertical"> - <com.android.contacts.common.list.ViewPagerTabs - android:id="@+id/viewpager_header" + + <LinearLayout + android:id="@+id/toolbar_container" android:layout_width="match_parent" - android:layout_height="@dimen/tab_height" - android:textAllCaps="true" - android:orientation="horizontal" - android:layout_gravity="top" + android:layout_height="wrap_content" android:elevation="@dimen/tab_elevation" - style="@style/DialtactsActionBarTabTextStyle" /> + android:orientation="vertical"> + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:background="@color/actionbar_background_color" /> + <com.android.contacts.common.list.ViewPagerTabs + android:id="@+id/viewpager_header" + android:layout_width="match_parent" + android:layout_height="@dimen/tab_height" + android:textAllCaps="true" + android:orientation="horizontal" + android:layout_gravity="top" + style="@style/DialtactsActionBarTabTextStyle" /> + + </LinearLayout> + <android.support.v4.view.ViewPager android:id="@+id/call_log_pager" android:layout_width="match_parent" diff --git a/res/menu/call_log_options.xml b/res/menu/call_log_options.xml index f0cbdf142..029c4da08 100644 --- a/res/menu/call_log_options.xml +++ b/res/menu/call_log_options.xml @@ -17,7 +17,8 @@ <item android:id="@+id/search_calllog" android:title="@string/calllog_search_hint" - android:showAsAction="never" + android:icon="@drawable/ic_ab_search" + android:showAsAction="always" android:orderInCategory="1"/> <item android:id="@+id/delete_all" diff --git a/res/values/styles.xml b/res/values/styles.xml index 195f9cd70..c5dcfc96e 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -99,6 +99,12 @@ <item name="android:actionOverflowButtonStyle">@style/DialtactsActionBarOverflowWhite</item> </style> + <style name="DialtactsThemeWithoutActionBar" parent="DialtactsThemeWithoutActionBarOverlay"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowActionBar">false</item> + <item name="android:toolbarStyle">@style/ToolbarStyle</item> + </style> + <!-- Hide the actionbar title during the activity preview --> <style name="DialtactsActivityTheme" parent="DialtactsTheme"> <item name="android:actionBarStyle">@style/DialtactsActionBarWithoutTitleStyle</item> @@ -106,12 +112,6 @@ <item name="android:fastScrollTrackDrawable">@null</item> </style> - <style name="DialtactsActionBarSpinner" - parent="@*android:style/Widget.Holo.Light.Spinner.DropDown.ActionBar"> - - <item name="android:background">@drawable/spinner_ab_holo_dark</item> - </style> - <style name="CallDetailActivityTheme1" parent="android:Theme.Holo.Light"> <item name="android:windowBackground">@color/background_dialer_list_items</item> <item name="android:gravity">top</item> @@ -167,6 +167,10 @@ <item name="android:fontFamily">"sans-serif-medium"</item> </style> + <style name="ToolbarStyle" parent="@android:style/Widget.Toolbar"> + <item name="android:titleTextAppearance">@style/DialtactsActionBarTitleText</item> + </style> + <style name="ListViewStyle" parent="@android:style/Widget.Material.Light.ListView"> <item name="android:overScrollMode">always</item> </style> diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java index a63674ef6..6eac998cf 100644 --- a/src/com/android/dialer/PhoneCallDetailsHelper.java +++ b/src/com/android/dialer/PhoneCallDetailsHelper.java @@ -26,14 +26,13 @@ import android.telephony.SubscriptionManager; import android.graphics.Typeface; import android.telecom.PhoneAccount; import android.text.SpannableString; -import android.text.Spanned; import android.text.TextUtils; import android.text.format.DateUtils; -import android.text.style.StyleSpan; import android.view.View; import android.widget.TextView; import com.android.contacts.common.CallUtil; +import com.android.contacts.common.format.TextHighlighter; import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.util.PhoneNumberHelper; import com.android.dialer.calllog.ContactInfo; @@ -60,6 +59,7 @@ public class PhoneCallDetailsHelper { // Helper classes. private final PhoneNumberDisplayHelper mPhoneNumberHelper; private final PhoneNumberUtilsWrapper mPhoneNumberUtilsWrapper; + private final TextHighlighter mHighlighter; /** * List of items to be concatenated together for accessibility descriptions @@ -79,6 +79,8 @@ public class PhoneCallDetailsHelper { mResources = resources; mPhoneNumberUtilsWrapper = phoneUtils; mPhoneNumberHelper = new PhoneNumberDisplayHelper(context, resources, phoneUtils); + mHighlighter = new TextHighlighter(Typeface.BOLD, + mResources.getColor(R.color.text_highlight_color)); } /** Fills the call details views with content. */ @@ -136,38 +138,25 @@ public class PhoneCallDetailsHelper { views.callAccountLabel.setVisibility(View.GONE); } - CharSequence nameText; - CharSequence displayNumber = - mPhoneNumberHelper.getDisplayNumber(details.accountHandle, details.number, - details.numberPresentation, details.formattedNumber); - String phoneNum = (String) details.number; - if (!TextUtils.isEmpty(filter) && phoneNum.contains(filter)) { - int start, end; - start = phoneNum.indexOf(filter); - end = start + filter.length(); - SpannableString result = new SpannableString(phoneNum); - result.setSpan(new StyleSpan(Typeface.BOLD), start, end, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - displayNumber = result; - } + final CharSequence nameText; if (TextUtils.isEmpty(details.name)) { - nameText = displayNumber; + nameText = mPhoneNumberHelper.getDisplayNumber(details.accountHandle, + details.number, details.numberPresentation, details.formattedNumber); // We have a real phone number as "nameView" so make it always LTR views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR); } else { nameText = details.name; - if (!TextUtils.isEmpty(filter) && nameText.toString().contains(filter)) { - int start,end; - start = nameText.toString().indexOf(filter); - end = start + filter.length(); - SpannableString style = new SpannableString(nameText); - style.setSpan(new StyleSpan(Typeface.BOLD), start, end, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - nameText = style; - } } - views.nameView.setText(nameText); + int filterIndex = filter != null && nameText != null + ? nameText.toString().toLowerCase().indexOf(filter.toLowerCase()) : -1; + if (filterIndex >= 0) { + SpannableString s = new SpannableString(nameText); + mHighlighter.applyMaskingHighlight(s, filterIndex, filterIndex + filter.length()); + views.nameView.setText(s); + } else { + views.nameView.setText(nameText); + } if (isVoicemail && !TextUtils.isEmpty(details.transcription)) { views.voicemailTranscriptionView.setText(details.transcription); diff --git a/src/com/android/dialer/calllog/CallLogActivity.java b/src/com/android/dialer/calllog/CallLogActivity.java index ae2d12e94..6f6dec474 100755 --- a/src/com/android/dialer/calllog/CallLogActivity.java +++ b/src/com/android/dialer/calllog/CallLogActivity.java @@ -15,107 +15,198 @@ m * limitations under the License. */ package com.android.dialer.calllog; +import android.animation.ValueAnimator; import android.app.ActionBar; -import android.app.ActionBar.LayoutParams; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; -import android.app.ListFragment; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.support.v13.app.FragmentPagerAdapter; +import android.os.Parcelable; +import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; +import android.text.Editable; import android.text.TextUtils; +import android.text.TextWatcher; import android.view.View; -import android.view.View.OnFocusChangeListener; import android.view.inputmethod.InputMethodManager; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.SearchView; -import android.widget.SearchView.OnCloseListener; -import android.widget.SearchView.OnQueryTextListener; +import android.widget.Toolbar; import com.android.contacts.common.interactions.TouchPointManager; import com.android.contacts.common.list.ViewPagerTabs; +import com.android.contacts.common.util.ViewUtil; import com.android.contacts.commonbind.analytics.AnalyticsUtil; import com.android.dialer.DialtactsActivity; import com.android.dialer.R; import com.android.dialer.callstats.CallStatsFragment; import com.android.dialer.widget.DoubleDatePickerDialog; -import com.android.dialer.voicemail.VoicemailStatusHelper; -import com.android.dialer.voicemail.VoicemailStatusHelperImpl; - public class CallLogActivity extends Activity implements - ViewPager.OnPageChangeListener, DoubleDatePickerDialog.OnDateSetListener { - private Handler mHandler; + ViewPager.OnPageChangeListener, SearchView.OnCloseListener, + View.OnClickListener, DoubleDatePickerDialog.OnDateSetListener { private ViewPager mViewPager; private ViewPagerTabs mViewPagerTabs; - private FragmentPagerAdapter mViewPagerAdapter; - private CallStatsFragment mStatsFragment; + private Toolbar mToolbar; + private MSimViewPagerAdapter mViewPagerAdapter; - private MSimCallLogFragment mMSimCallsFragment; + private MSimCallLogFragment mCallsFragment; + private CallStatsFragment mStatsFragment; private CallLogSearchFragment mSearchFragment; - private SearchView mSearchView; - private boolean mInSearchUi; + + private int mCurrentTab; + private int mMaxTabHeight; + private int mMaxToolbarContentInsetStart; + + private boolean mSearchMode; + private String mQueryString; + + private EditText mSearchView; + private View mSearchContainer; private static final int TAB_INDEX_MSIM = 0; private static final int TAB_INDEX_MSIM_STATS = 1; private static final int TAB_INDEX_COUNT_MSIM = 2; - public class MSimViewPagerAdapter extends FragmentPagerAdapter { + private static final String EXTRA_KEY_SEARCH_MODE = "searchMode"; + private static final String EXTRA_KEY_QUERY = "query"; + private static final String EXTRA_KEY_SELECTED_TAB = "selectedTab"; + + public class MSimViewPagerAdapter extends PagerAdapter { + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + private Fragment mCurrentPrimaryItem; private String[] mTabTitles; + private boolean mPagerInSearchMode; public MSimViewPagerAdapter(FragmentManager fm) { - super(fm); + mFragmentManager = fm; mTabTitles = new String[TAB_INDEX_COUNT_MSIM]; mTabTitles[0] = getString(R.string.call_log_all_title); mTabTitles[1] = getString(R.string.call_log_stats_title); } + public void setSearchMode(boolean searchMode) { + mPagerInSearchMode = searchMode; + notifyDataSetChanged(); + } + + public boolean isSearchMode() { + return mPagerInSearchMode; + } + @Override - public Fragment getItem(int position) { - switch (position) { - case TAB_INDEX_MSIM: - MSimCallLogFragment ms = new MSimCallLogFragment(); - ms.setHasOptionsMenu(true); - return ms; - case TAB_INDEX_MSIM_STATS: - return new CallStatsFragment(); + public int getCount() { + return mPagerInSearchMode ? 1 : TAB_INDEX_COUNT_MSIM; + } + + @Override + public CharSequence getPageTitle(int position) { + return mTabTitles[position]; + } + + /** Gets called when the number of items changes. */ + @Override + public int getItemPosition(Object object) { + if (mPagerInSearchMode) { + if (object == mSearchFragment) { + return 0; + } + } else { + if (object == mCallsFragment) { + return TAB_INDEX_MSIM; + } + if (object == mStatsFragment) { + return TAB_INDEX_MSIM_STATS; + } } - throw new IllegalStateException("No fragment at position " + position); + return POSITION_NONE; } @Override public Object instantiateItem(ViewGroup container, int position) { - final ListFragment fragment = - (ListFragment) super.instantiateItem(container, position); - switch (position) { - case TAB_INDEX_MSIM: - mMSimCallsFragment = (MSimCallLogFragment)fragment; - break; - case TAB_INDEX_MSIM_STATS: - mStatsFragment = (CallStatsFragment)fragment; - break; + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); } - return fragment; + Fragment f = getFragment(position); + mCurTransaction.show(f); + + // Non primary pages are not visible. + f.setUserVisibleHint(f == mCurrentPrimaryItem); + return f; } @Override - public CharSequence getPageTitle(int position) { - return mTabTitles[position]; + public void destroyItem(ViewGroup container, int position, Object object) { + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + mCurTransaction.hide((Fragment) object); } @Override - public int getCount() { - return TAB_INDEX_COUNT_MSIM; + public void startUpdate(ViewGroup container) { + } + + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + invalidateOptionsMenu(); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment) object).getView() == view; + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + if (mCurrentPrimaryItem != fragment) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public Parcelable saveState() { + return null; + } + + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + } + + private Fragment getFragment(int position) { + if (mPagerInSearchMode) { + return mSearchFragment; + } + switch (position) { + case TAB_INDEX_MSIM: + return mCallsFragment; + case TAB_INDEX_MSIM_STATS: + return mStatsFragment; + } + throw new IllegalStateException("No fragment at position " + position); } } @@ -134,11 +225,16 @@ public class CallLogActivity extends Activity implements setContentView(R.layout.call_log_activity); getWindow().setBackgroundDrawable(null); - final ActionBar actionBar = getActionBar(); - actionBar.setDisplayShowHomeEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setElevation(0); + mToolbar = (Toolbar) findViewById(R.id.toolbar); + mMaxToolbarContentInsetStart = mToolbar.getContentInsetStart(); + + setActionBar(mToolbar); + setupSearchViews(); + + final View toolbarContainer = findViewById(R.id.toolbar_container); + ViewUtil.addRectangularOutlineProvider(toolbarContainer, getResources()); + + mMaxTabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height); mViewPager = (ViewPager) findViewById(R.id.call_log_pager); @@ -148,14 +244,23 @@ public class CallLogActivity extends Activity implements mViewPagerTabs = (ViewPagerTabs) findViewById(R.id.viewpager_header); mViewPager.setOnPageChangeListener(mViewPagerTabs); mViewPagerTabs.setViewPager(mViewPager); - addSearchFragment(); + + setupFragments(); + + if (savedInstanceState != null) { + setQueryString(savedInstanceState.getString(EXTRA_KEY_QUERY)); + mSearchMode = savedInstanceState.getBoolean(EXTRA_KEY_SEARCH_MODE); + mCurrentTab = savedInstanceState.getInt(EXTRA_KEY_SELECTED_TAB); + } + + update(true); } @Override - public void onAttachFragment(Fragment fragment) { - if (fragment instanceof CallLogSearchFragment) { - mSearchFragment = (CallLogSearchFragment) fragment; - } + protected void onSaveInstanceState(Bundle outState) { + outState.putBoolean(EXTRA_KEY_SEARCH_MODE, mSearchMode); + outState.putString(EXTRA_KEY_QUERY, mQueryString); + outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab); } @Override @@ -169,25 +274,15 @@ public class CallLogActivity extends Activity implements public boolean onPrepareOptionsMenu(Menu menu) { final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all); final MenuItem itemSearchCallLog = menu.findItem(R.id.search_calllog); - if (mInSearchUi) { - if (itemDeleteAll != null) { - itemDeleteAll.setVisible(false); - } - if (itemSearchCallLog != null) { - itemSearchCallLog.setVisible(false); - } + + if (mSearchMode) { + itemSearchCallLog.setVisible(false); + itemDeleteAll.setVisible(false); } else { - if (mSearchFragment != null && itemSearchCallLog != null) { - final CallLogAdapter adapter = mSearchFragment.getAdapter(); - itemSearchCallLog.setVisible(adapter != null - && !adapter.isEmpty()); - } + itemSearchCallLog.setVisible(true); - // If onPrepareOptionsMenu is called before fragments loaded. Don't do anything. - if (mMSimCallsFragment != null && itemDeleteAll != null) { - final CallLogAdapter adapter = mMSimCallsFragment.getAdapter(); - itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty()); - } + final CallLogAdapter adapter = mCallsFragment.getAdapter(); + itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty()); } return true; } @@ -204,222 +299,286 @@ public class CallLogActivity extends Activity implements onDelCallLog(); return true; case R.id.search_calllog: - enterSearchUi(); + onSearchRequested(); return true; } return super.onOptionsItemSelected(item); } + @Override + public boolean onSearchRequested() { // Search key pressed. + setSearchMode(true); + return true; + } + + @Override + public boolean onClose() { + setSearchMode(false); + return false; + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.search_close_button: + setQueryString(null); + break; + case R.id.search_back_button: + onBackPressed(); + break; + } + } + + private void setupFragments() { + final String CALLS_TAG = "tab-pager-calls"; + final String STATS_TAG = "tab-pager-stats"; + final String SEARCH_TAG = "tab-pager-search"; + + final FragmentManager fm = getFragmentManager(); + final FragmentTransaction transaction = fm.beginTransaction(); + + // Create the fragments and add as children of the view pager. + // The pager adapter will only change the visibility; it'll never create/destroy + // fragments. + // However, if it's after screen rotation, the fragments have been re-created by + // the fragment manager, so first see if there're already the target fragments + // existing. + mCallsFragment = (MSimCallLogFragment) fm.findFragmentByTag(CALLS_TAG); + mStatsFragment = (CallStatsFragment) fm.findFragmentByTag(STATS_TAG); + mSearchFragment = (CallLogSearchFragment) fm.findFragmentByTag(SEARCH_TAG); + + if (mCallsFragment == null) { + mCallsFragment = new MSimCallLogFragment(); + mStatsFragment = new CallStatsFragment(); + mSearchFragment = new CallLogSearchFragment(); + + transaction.add(R.id.call_log_pager, mCallsFragment, CALLS_TAG); + transaction.add(R.id.call_log_pager, mStatsFragment, STATS_TAG); + transaction.add(R.id.call_log_pager, mSearchFragment, SEARCH_TAG); + } + + transaction.hide(mCallsFragment); + transaction.hide(mStatsFragment); + transaction.hide(mSearchFragment); + + transaction.commitAllowingStateLoss(); + fm.executePendingTransactions(); + } + + private void setupSearchViews() { + final LayoutInflater inflater = + (LayoutInflater) mToolbar.getContext().getSystemService(LAYOUT_INFLATER_SERVICE); + mSearchContainer = inflater.inflate(R.layout.search_bar_expanded, mToolbar, false); + mSearchContainer.setVisibility(View.VISIBLE); + mSearchContainer.setBackgroundColor( + getResources().getColor(R.color.searchbox_background_color)); + + mSearchView = (EditText) mSearchContainer.findViewById(R.id.search_view); + mSearchView.setHint(getString(R.string.calllog_search_hint)); + mSearchView.addTextChangedListener(new SearchTextWatcher()); + mSearchContainer.findViewById(R.id.search_close_button).setOnClickListener(this); + mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(this); + } + private void onDelCallLog() { Intent intent = new Intent( "com.android.contacts.action.MULTI_PICK_CALL"); startActivity(intent); } - private void enterSearchUi() { - if (mSearchFragment == null) { - return; - } - if (mSearchView == null) { - prepareSearchView(); - } - final ActionBar actionBar = getActionBar(); - - mSearchView.setQuery(null, true); + private void setFocusOnSearchView() { mSearchView.requestFocus(); + showInputMethod(mSearchView); // Workaround for the "IME not popping up" issue. + } - actionBar.setDisplayShowCustomEnabled(true); + private void showInputMethod(View view) { + final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } - for (int i = 0; i < mViewPagerAdapter.getCount(); i++) { - updateFragmentVisibility(i, false /* not visible */); + private void update(boolean skipAnimation) { + final boolean isIconifiedChanging = (mSearchContainer.getParent() == null) == mSearchMode; + if (!isIconifiedChanging) { + updateDisplayOptions(false); + return; } - mSearchFragment.setUserVisibleHint(true); - final FragmentTransaction transaction = getFragmentManager() - .beginTransaction(); - transaction.show(mSearchFragment); - transaction.commitAllowingStateLoss(); - getFragmentManager().executePendingTransactions(); - mViewPager.setVisibility(View.GONE); - mViewPagerTabs.setVisibility(View.GONE); - - // We need to call this and onActionViewCollapsed() manually, since we - // are using a custom - // layout instead of asking the search menu item to take care of - // SearchView. - mSearchView.onActionViewExpanded(); - mInSearchUi = true; + if (skipAnimation) { + if (mSearchMode) { + setTabHeight(0); + addSearchContainer(); + } else { + setTabHeight(mMaxTabHeight); + mToolbar.removeView(mSearchContainer); + } + updateDisplayOptions(true); + } else { + if (mSearchMode) { + addSearchContainer(); + mSearchContainer.setAlpha(0); + mSearchContainer.animate().alpha(1); + animateTabHeightChange(mMaxTabHeight, 0); + updateDisplayOptions(true); + } else { + mSearchContainer.setAlpha(1); + animateTabHeightChange(0, mMaxTabHeight); + mSearchContainer.animate().alpha(0).withEndAction(new Runnable() { + @Override + public void run() { + updateDisplayOptions(true); + mToolbar.removeView(mSearchContainer); + } + }); + } + } } - private void updateFragmentVisibility(int position, boolean visibility) { - if (position >= TAB_INDEX_MSIM) { - final Fragment fragment = getFragmentAt(position); - if (fragment != null) { - fragment.setMenuVisibility(visibility); - fragment.setUserVisibleHint(visibility); + 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); + } } + } else if (mViewPager.getCurrentItem() != mCurrentTab) { + mViewPager.setCurrentItem(mCurrentTab, !mViewPagerAdapter.isSearchMode()); } + + updateDisplayOptionsInner(); + mViewPagerAdapter.setSearchMode(mSearchMode); } - private Fragment getFragmentAt(int position) { - switch (position) { - case TAB_INDEX_MSIM: - return mMSimCallsFragment; - case TAB_INDEX_MSIM_STATS: - return mStatsFragment; - default: - throw new IllegalStateException("Unknown fragment index: " - + position); + 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 ActionBar actionBar = getActionBar(); + final int current = actionBar.getDisplayOptions() & MASK; + + // Build the new flags... + int newFlags = 0; + if (mSearchMode) { + newFlags |= ActionBar.DISPLAY_SHOW_CUSTOM; + mToolbar.setContentInsetsRelative(0, mToolbar.getContentInsetEnd()); + } else { + newFlags |= ActionBar.DISPLAY_SHOW_TITLE; + newFlags |= ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_HOME_AS_UP; + mToolbar.setContentInsetsRelative(mMaxToolbarContentInsetStart, + mToolbar.getContentInsetEnd()); } + + + if (current != newFlags) { + // Pass the mask here to preserve other flags that we're not interested here. + actionBar.setDisplayOptions(newFlags, MASK); + } + } + + private void addSearchContainer() { + mToolbar.removeView(mSearchContainer); + mToolbar.addView(mSearchContainer); } - private void addSearchFragment() { - if (mSearchFragment != null) { + private void animateTabHeightChange(int start, int end) { + if (mViewPagerTabs == null) { return; } - final FragmentTransaction ft = getFragmentManager().beginTransaction(); - final Fragment searchFragment = new CallLogSearchFragment(); - searchFragment.setUserVisibleHint(false); - ft.add(R.id.calllog_frame, searchFragment); - ft.hide(searchFragment); - ft.commitAllowingStateLoss(); + final ValueAnimator animator = ValueAnimator.ofInt(start, end); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + int value = (Integer) valueAnimator.getAnimatedValue(); + setTabHeight(value); + } + }); + animator.setDuration(100).start(); } - private void prepareSearchView() { - final View searchViewLayout = getLayoutInflater().inflate( - R.layout.custom_action_bar, null); - mSearchView = (SearchView) searchViewLayout - .findViewById(R.id.search_view); - mSearchView.setOnQueryTextListener(mPhoneSearchQueryTextListener); - mSearchView.setOnCloseListener(mPhoneSearchCloseListener); - mSearchView.setQueryHint(getString(R.string.calllog_search_hint)); - mSearchView.setIconifiedByDefault(true); - mSearchView.setIconified(false); - - mSearchView - .setOnQueryTextFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (hasFocus) { - showInputMethod(view.findFocus()); - } - } - }); + private void setTabHeight(int height) { + if (mViewPagerTabs == null) { + return; + } + ViewGroup.LayoutParams layoutParams = mViewPagerTabs.getLayoutParams(); + layoutParams.height = height; + mViewPagerTabs.setLayoutParams(layoutParams); + } - getActionBar().setCustomView( - searchViewLayout, - new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT)); + private void setQueryString(String query) { + mQueryString = query; + if (mSearchView != null) { + mSearchView.setText(query); + // When programmatically entering text into the search view, the most reasonable + // place for the cursor is after all the text. + mSearchView.setSelection(mSearchView.getText() == null ? + 0 : mSearchView.getText().length()); + } } - private void showInputMethod(View view) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) { - if (!imm.showSoftInput(view, 0)) { + private void setSearchMode(boolean flag) { + if (mSearchMode == flag) { + if (flag) { + setFocusOnSearchView(); } + return; } - } - private void hideInputMethod(View view) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null && view != null) { - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + mSearchMode = flag; + update(false); + if (flag) { + mSearchView.setEnabled(true); + setFocusOnSearchView(); + } else { + mSearchView.setEnabled(false); } + + setQueryString(null); } - /** - * Listener used to send search queries to the phone search fragment. - */ - private final OnQueryTextListener mPhoneSearchQueryTextListener = new OnQueryTextListener() { + private class SearchTextWatcher implements TextWatcher { @Override - public boolean onQueryTextSubmit(String query) { - View view = getCurrentFocus(); - if (view != null) { - hideInputMethod(view); - view.clearFocus(); + public void onTextChanged(CharSequence queryString, int start, int before, int count) { + if (queryString.equals(mQueryString)) { + return; + } + mQueryString = queryString.toString(); + mSearchFragment.setQueryString(mQueryString); + if (!mSearchMode && !TextUtils.isEmpty(queryString)) { + setSearchMode(true); } - return true; } @Override - public boolean onQueryTextChange(String newText) { - // Show search result with non-empty text. Show a bare list - // otherwise. - if (mSearchFragment != null) { - mSearchFragment.setQueryString(newText); - } - return true; - } - }; + public void afterTextChanged(Editable s) {} - /** - * Listener used to handle the "close" button on the right side of - * {@link SearchView}. If some text is in the search view, this will clean - * it up. Otherwise this will exit the search UI and let users go back to - * usual Phone UI. - * - * This does _not_ handle back button. - */ - private final OnCloseListener mPhoneSearchCloseListener = new OnCloseListener() { @Override - public boolean onClose() { - if (!TextUtils.isEmpty(mSearchView.getQuery())) { - mSearchView.setQuery(null, true); - } - return true; - } - }; + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + } @Override public void onBackPressed() { - if (mInSearchUi) { + if (mSearchMode) { // We should let the user go back to usual screens with tabs. - exitSearchUi(); + setSearchMode(false); } else { super.onBackPressed(); } } - private void exitSearchUi() { - final ActionBar actionBar = getActionBar(); - if (mSearchFragment != null) { - mSearchFragment.setUserVisibleHint(false); - - final FragmentTransaction transaction = getFragmentManager() - .beginTransaction(); - transaction.hide(mSearchFragment); - transaction.commitAllowingStateLoss(); - - } - - // We want to hide SearchView and show Tabs. Also focus on previously - // selected one. - actionBar.setDisplayShowCustomEnabled(false); - - for (int i = 0; i < mViewPagerAdapter.getCount(); i++) { - updateFragmentVisibility(i, i == mViewPager.getCurrentItem()); - } - - mViewPager.setVisibility(View.VISIBLE); - mViewPagerTabs.setVisibility(View.VISIBLE); - - hideInputMethod(getCurrentFocus()); - - // Request to update option menu. - invalidateOptionsMenu(); - - // See comments in onActionViewExpanded() - mSearchView.onActionViewCollapsed(); - mSearchView.clearFocus(); - mInSearchUi = false; - } - @Override public void onDateSet(long from, long to) { switch (mViewPager.getCurrentItem()) { case TAB_INDEX_MSIM: - mMSimCallsFragment.onDateSet(from, to); + mCallsFragment.onDateSet(from, to); break; case TAB_INDEX_MSIM_STATS: mStatsFragment.onDateSet(from, to); @@ -438,6 +597,9 @@ public class CallLogActivity extends Activity implements sendScreenViewForChildFragment(position); } mViewPagerTabs.onPageSelected(position); + if (!mSearchMode) { + mCurrentTab = position; + } } @Override diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java index 7f8a33db2..05fd69855 100755 --- a/src/com/android/dialer/calllog/CallLogAdapter.java +++ b/src/com/android/dialer/calllog/CallLogAdapter.java @@ -467,7 +467,7 @@ public class CallLogAdapter extends GroupingListAdapter sourceType, accountHandle, features, dataUsage, transcription); } - mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details); + mCallLogViewsHelper.setPhoneCallDetails(mContext, views, details, mFilterString); int contactType = ContactPhotoManager.TYPE_DEFAULT; diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java index 77ad333d9..b0e0e5a8f 100644 --- a/src/com/android/dialer/calllog/CallLogListItemHelper.java +++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java @@ -60,10 +60,11 @@ import com.android.dialer.R; * @param context The application context. * @param views the views to populate * @param details the details of a phone call needed to fill in the data + * @param filter A filter string to highlight (or null if there's no filter active) */ public void setPhoneCallDetails( - Context context, CallLogListItemViews views, PhoneCallDetails details) { - mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details); + Context context, CallLogListItemViews views, PhoneCallDetails details, String filter) { + mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, filter); // Set the accessibility text for the contact badge views.quickContactView.setContentDescription(getContactBadgeDescription(details)); diff --git a/src/com/android/dialer/calllog/MSimCallLogFragment.java b/src/com/android/dialer/calllog/MSimCallLogFragment.java index 193059a4c..4676678c8 100644 --- a/src/com/android/dialer/calllog/MSimCallLogFragment.java +++ b/src/com/android/dialer/calllog/MSimCallLogFragment.java @@ -148,6 +148,12 @@ public class MSimCallLogFragment extends CallLogFragment } @Override + public void onActivityCreated(Bundle savedState) { + setHasOptionsMenu(true); + super.onActivityCreated(savedState); + } + + @Override public void fetchCalls() { fetchCalls(mFilterFrom, mFilterTo, mCallSlotFilter); } @@ -220,9 +226,11 @@ public class MSimCallLogFragment extends CallLogFragment public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.call_log_fragment_options, menu); - MenuItem resetItem = menu.findItem(R.id.reset_date_filter); - resetItem.setVisible(mFilterFrom != -1); + if (getUserVisibleHint()) { + inflater.inflate(R.menu.call_log_fragment_options, menu); + MenuItem resetItem = menu.findItem(R.id.reset_date_filter); + resetItem.setVisible(mFilterFrom != -1); + } } @Override diff --git a/src/com/android/dialer/callstats/CallStatsFragment.java b/src/com/android/dialer/callstats/CallStatsFragment.java index 89a8504a1..fcfcbdafd 100644 --- a/src/com/android/dialer/callstats/CallStatsFragment.java +++ b/src/com/android/dialer/callstats/CallStatsFragment.java @@ -156,15 +156,17 @@ public class CallStatsFragment extends ListFragment implements @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.call_stats_options, menu); + if (getUserVisibleHint()) { + inflater.inflate(R.menu.call_stats_options, menu); - final MenuItem resetItem = menu.findItem(R.id.reset_date_filter); - final MenuItem sortDurationItem = menu.findItem(R.id.sort_by_duration); - final MenuItem sortCountItem = menu.findItem(R.id.sort_by_count); + final MenuItem resetItem = menu.findItem(R.id.reset_date_filter); + final MenuItem sortDurationItem = menu.findItem(R.id.sort_by_duration); + final MenuItem sortCountItem = menu.findItem(R.id.sort_by_count); - resetItem.setVisible(mFilterFrom != -1); - sortDurationItem.setVisible(!mSortByDuration); - sortCountItem.setVisible(mSortByDuration); + resetItem.setVisible(mFilterFrom != -1); + sortDurationItem.setVisible(!mSortByDuration); + sortCountItem.setVisible(mSortByDuration); + } super.onCreateOptionsMenu(menu, inflater); } diff --git a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java index 30a84c2f3..b14eed166 100644 --- a/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java +++ b/tests/src/com/android/dialer/calllog/CallLogListItemHelperTest.java @@ -353,7 +353,8 @@ public class CallLogListItemHelperTest extends AndroidTestCase { mHelper.setPhoneCallDetails(getContext(), mViews, new PhoneCallDetails(number, presentation, formattedNumber, TEST_COUNTRY_ISO, TEST_GEOCODE, - new int[]{ callType }, TEST_DATE, TEST_DURATION) + new int[]{ callType }, TEST_DATE, TEST_DURATION), + null ); } @@ -362,7 +363,8 @@ public class CallLogListItemHelperTest extends AndroidTestCase { mHelper.setPhoneCallDetails(getContext() ,mViews, new PhoneCallDetails(TEST_NUMBER, Calls.PRESENTATION_ALLOWED, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO, TEST_GEOCODE, - types, TEST_DATE, TEST_DURATION) + types, TEST_DATE, TEST_DURATION), + null ); } @@ -371,7 +373,8 @@ public class CallLogListItemHelperTest extends AndroidTestCase { mHelper.setPhoneCallDetails(getContext(), mViews, new PhoneCallDetails(TEST_NUMBER, Calls.PRESENTATION_ALLOWED, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO, TEST_GEOCODE, - types, TEST_DATE, TEST_DURATION) + types, TEST_DATE, TEST_DURATION), + null ); } } |