CallLogFragment.java revision 0a3c7a196fe7fa0622b59b62e46ba10cbfe2968a
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.dialer.calllog; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.app.Activity; 23import android.app.DialogFragment; 24import android.app.KeyguardManager; 25import android.content.Context; 26import android.content.Intent; 27import android.database.ContentObserver; 28import android.database.Cursor; 29import android.graphics.Rect; 30import android.os.Bundle; 31import android.os.Handler; 32import android.provider.CallLog; 33import android.provider.CallLog.Calls; 34import android.provider.ContactsContract; 35import android.provider.VoicemailContract.Status; 36import android.util.MutableInt; 37import android.view.LayoutInflater; 38import android.view.View; 39import android.view.ViewGroup; 40import android.view.ViewTreeObserver; 41import android.view.View.OnClickListener; 42import android.view.ViewGroup.LayoutParams; 43import android.widget.ListView; 44import android.widget.TextView; 45 46import com.android.contacts.common.GeoUtil; 47import com.android.contacts.common.util.ViewUtil; 48import com.android.dialer.R; 49import com.android.dialer.list.ListsFragment.HostInterface; 50import com.android.dialer.util.DialerUtils; 51import com.android.dialer.util.EmptyLoader; 52import com.android.dialer.voicemail.VoicemailStatusHelper; 53import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage; 54import com.android.dialer.voicemail.VoicemailStatusHelperImpl; 55import com.android.dialerbind.ObjectFactory; 56import com.android.dialerbind.analytics.AnalyticsListFragment; 57 58import java.util.List; 59 60/** 61 * Displays a list of call log entries. To filter for a particular kind of call 62 * (all, missed or voicemails), specify it in the constructor. 63 */ 64public class CallLogFragment extends AnalyticsListFragment 65 implements CallLogQueryHandler.Listener, CallLogAdapter.OnReportButtonClickListener, 66 CallLogAdapter.CallFetcher, 67 CallLogAdapter.CallItemExpandedListener { 68 private static final String TAG = "CallLogFragment"; 69 70 private static final String REPORT_DIALOG_TAG = "report_dialog"; 71 private String mReportDialogNumber; 72 private boolean mIsReportDialogShowing; 73 74 /** 75 * ID of the empty loader to defer other fragments. 76 */ 77 private static final int EMPTY_LOADER_ID = 0; 78 79 private static final String KEY_FILTER_TYPE = "filter_type"; 80 private static final String KEY_LOG_LIMIT = "log_limit"; 81 private static final String KEY_DATE_LIMIT = "date_limit"; 82 private static final String KEY_SHOW_FOOTER = "show_footer"; 83 private static final String KEY_IS_REPORT_DIALOG_SHOWING = "is_report_dialog_showing"; 84 private static final String KEY_REPORT_DIALOG_NUMBER = "report_dialog_number"; 85 86 private CallLogAdapter mAdapter; 87 private CallLogQueryHandler mCallLogQueryHandler; 88 private boolean mScrollToTop; 89 90 /** Whether there is at least one voicemail source installed. */ 91 private boolean mVoicemailSourcesAvailable = false; 92 93 private VoicemailStatusHelper mVoicemailStatusHelper; 94 private View mStatusMessageView; 95 private TextView mStatusMessageText; 96 private TextView mStatusMessageAction; 97 private KeyguardManager mKeyguardManager; 98 private View mFooterView; 99 100 private boolean mEmptyLoaderRunning; 101 private boolean mCallLogFetched; 102 private boolean mVoicemailStatusFetched; 103 104 private float mExpandedItemTranslationZ; 105 private int mFadeInDuration; 106 private int mFadeInStartDelay; 107 private int mFadeOutDuration; 108 private int mExpandCollapseDuration; 109 110 private final Handler mHandler = new Handler(); 111 112 private class CustomContentObserver extends ContentObserver { 113 public CustomContentObserver() { 114 super(mHandler); 115 } 116 @Override 117 public void onChange(boolean selfChange) { 118 mRefreshDataRequired = true; 119 } 120 } 121 122 // See issue 6363009 123 private final ContentObserver mCallLogObserver = new CustomContentObserver(); 124 private final ContentObserver mContactsObserver = new CustomContentObserver(); 125 private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver(); 126 private boolean mRefreshDataRequired = true; 127 128 // Exactly same variable is in Fragment as a package private. 129 private boolean mMenuVisible = true; 130 131 // Default to all calls. 132 private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL; 133 134 // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler} 135 // will be used. 136 private int mLogLimit = -1; 137 138 // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after 139 // the date filter are included. If zero, no date-based filtering occurs. 140 private long mDateLimit = 0; 141 142 // Whether or not to show the Show call history footer view 143 private boolean mHasFooterView = false; 144 145 public CallLogFragment() { 146 this(CallLogQueryHandler.CALL_TYPE_ALL, -1); 147 } 148 149 public CallLogFragment(int filterType) { 150 this(filterType, -1); 151 } 152 153 public CallLogFragment(int filterType, int logLimit) { 154 super(); 155 mCallTypeFilter = filterType; 156 mLogLimit = logLimit; 157 } 158 159 /** 160 * Creates a call log fragment, filtering to include only calls of the desired type, occurring 161 * after the specified date. 162 * @param filterType type of calls to include. 163 * @param dateLimit limits results to calls occurring on or after the specified date. 164 */ 165 public CallLogFragment(int filterType, long dateLimit) { 166 this(filterType, -1, dateLimit); 167 } 168 169 /** 170 * Creates a call log fragment, filtering to include only calls of the desired type, occurring 171 * after the specified date. Also provides a means to limit the number of results returned. 172 * @param filterType type of calls to include. 173 * @param logLimit limits the number of results to return. 174 * @param dateLimit limits results to calls occurring on or after the specified date. 175 */ 176 public CallLogFragment(int filterType, int logLimit, long dateLimit) { 177 this(filterType, logLimit); 178 mDateLimit = dateLimit; 179 } 180 181 @Override 182 public void onCreate(Bundle state) { 183 super.onCreate(state); 184 if (state != null) { 185 mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter); 186 mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit); 187 mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit); 188 mHasFooterView = state.getBoolean(KEY_SHOW_FOOTER, mHasFooterView); 189 mIsReportDialogShowing = state.getBoolean(KEY_IS_REPORT_DIALOG_SHOWING, 190 mIsReportDialogShowing); 191 mReportDialogNumber = state.getString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber); 192 } 193 194 String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); 195 mAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, 196 new ContactInfoHelper(getActivity(), currentCountryIso), this, this, true); 197 setListAdapter(mAdapter); 198 mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), 199 this, mLogLimit); 200 mKeyguardManager = 201 (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); 202 getActivity().getContentResolver().registerContentObserver(CallLog.CONTENT_URI, true, 203 mCallLogObserver); 204 getActivity().getContentResolver().registerContentObserver( 205 ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver); 206 getActivity().getContentResolver().registerContentObserver( 207 Status.CONTENT_URI, true, mVoicemailStatusObserver); 208 setHasOptionsMenu(true); 209 updateCallList(mCallTypeFilter, mDateLimit); 210 211 mExpandedItemTranslationZ = 212 getResources().getDimension(R.dimen.call_log_expanded_translation_z); 213 mFadeInDuration = getResources().getInteger(R.integer.call_log_actions_fade_in_duration); 214 mFadeInStartDelay = getResources().getInteger(R.integer.call_log_actions_fade_start); 215 mFadeOutDuration = getResources().getInteger(R.integer.call_log_actions_fade_out_duration); 216 mExpandCollapseDuration = getResources().getInteger( 217 R.integer.call_log_expand_collapse_duration); 218 219 if (mIsReportDialogShowing) { 220 DialogFragment df = ObjectFactory.getReportDialogFragment(mReportDialogNumber); 221 if (df != null) { 222 df.setTargetFragment(this, 0); 223 df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG); 224 } 225 } 226 } 227 228 /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */ 229 @Override 230 public boolean onCallsFetched(Cursor cursor) { 231 if (getActivity() == null || getActivity().isFinishing()) { 232 // Return false; we did not take ownership of the cursor 233 return false; 234 } 235 mAdapter.setLoading(false); 236 mAdapter.changeCursor(cursor); 237 // This will update the state of the "Clear call log" menu item. 238 getActivity().invalidateOptionsMenu(); 239 if (mScrollToTop) { 240 final ListView listView = getListView(); 241 // The smooth-scroll animation happens over a fixed time period. 242 // As a result, if it scrolls through a large portion of the list, 243 // each frame will jump so far from the previous one that the user 244 // will not experience the illusion of downward motion. Instead, 245 // if we're not already near the top of the list, we instantly jump 246 // near the top, and animate from there. 247 if (listView.getFirstVisiblePosition() > 5) { 248 listView.setSelection(5); 249 } 250 // Workaround for framework issue: the smooth-scroll doesn't 251 // occur if setSelection() is called immediately before. 252 mHandler.post(new Runnable() { 253 @Override 254 public void run() { 255 if (getActivity() == null || getActivity().isFinishing()) { 256 return; 257 } 258 listView.smoothScrollToPosition(0); 259 } 260 }); 261 262 mScrollToTop = false; 263 } 264 mCallLogFetched = true; 265 destroyEmptyLoaderIfAllDataFetched(); 266 return true; 267 } 268 269 /** 270 * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider. 271 */ 272 @Override 273 public void onVoicemailStatusFetched(Cursor statusCursor) { 274 if (getActivity() == null || getActivity().isFinishing()) { 275 return; 276 } 277 updateVoicemailStatusMessage(statusCursor); 278 279 int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor); 280 setVoicemailSourcesAvailable(activeSources != 0); 281 mVoicemailStatusFetched = true; 282 destroyEmptyLoaderIfAllDataFetched(); 283 } 284 285 private void destroyEmptyLoaderIfAllDataFetched() { 286 if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) { 287 mEmptyLoaderRunning = false; 288 getLoaderManager().destroyLoader(EMPTY_LOADER_ID); 289 } 290 } 291 292 /** Sets whether there are any voicemail sources available in the platform. */ 293 private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) { 294 if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return; 295 mVoicemailSourcesAvailable = voicemailSourcesAvailable; 296 297 Activity activity = getActivity(); 298 if (activity != null) { 299 // This is so that the options menu content is updated. 300 activity.invalidateOptionsMenu(); 301 } 302 } 303 304 @Override 305 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { 306 View view = inflater.inflate(R.layout.call_log_fragment, container, false); 307 mVoicemailStatusHelper = new VoicemailStatusHelperImpl(); 308 mStatusMessageView = view.findViewById(R.id.voicemail_status); 309 mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message); 310 mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action); 311 return view; 312 } 313 314 @Override 315 public void onViewCreated(View view, Bundle savedInstanceState) { 316 super.onViewCreated(view, savedInstanceState); 317 getListView().setEmptyView(view.findViewById(R.id.empty_list_view)); 318 getListView().setItemsCanFocus(true); 319 maybeAddFooterView(); 320 321 updateEmptyMessage(mCallTypeFilter); 322 } 323 324 /** 325 * Based on the new intent, decide whether the list should be configured 326 * to scroll up to display the first item. 327 */ 328 public void configureScreenFromIntent(Intent newIntent) { 329 // Typically, when switching to the call-log we want to show the user 330 // the same section of the list that they were most recently looking 331 // at. However, under some circumstances, we want to automatically 332 // scroll to the top of the list to present the newest call items. 333 // For example, immediately after a call is finished, we want to 334 // display information about that call. 335 mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType()); 336 } 337 338 @Override 339 public void onStart() { 340 // Start the empty loader now to defer other fragments. We destroy it when both calllog 341 // and the voicemail status are fetched. 342 getLoaderManager().initLoader(EMPTY_LOADER_ID, null, 343 new EmptyLoader.Callback(getActivity())); 344 mEmptyLoaderRunning = true; 345 super.onStart(); 346 } 347 348 @Override 349 public void onResume() { 350 super.onResume(); 351 refreshData(); 352 } 353 354 private void updateVoicemailStatusMessage(Cursor statusCursor) { 355 List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor); 356 if (messages.size() == 0) { 357 mStatusMessageView.setVisibility(View.GONE); 358 } else { 359 mStatusMessageView.setVisibility(View.VISIBLE); 360 // TODO: Change the code to show all messages. For now just pick the first message. 361 final StatusMessage message = messages.get(0); 362 if (message.showInCallLog()) { 363 mStatusMessageText.setText(message.callLogMessageId); 364 } 365 if (message.actionMessageId != -1) { 366 mStatusMessageAction.setText(message.actionMessageId); 367 } 368 if (message.actionUri != null) { 369 mStatusMessageAction.setVisibility(View.VISIBLE); 370 mStatusMessageAction.setOnClickListener(new View.OnClickListener() { 371 @Override 372 public void onClick(View v) { 373 getActivity().startActivity( 374 new Intent(Intent.ACTION_VIEW, message.actionUri)); 375 } 376 }); 377 } else { 378 mStatusMessageAction.setVisibility(View.GONE); 379 } 380 } 381 } 382 383 @Override 384 public void onPause() { 385 super.onPause(); 386 // Kill the requests thread 387 mAdapter.stopRequestProcessing(); 388 } 389 390 @Override 391 public void onStop() { 392 super.onStop(); 393 updateOnExit(); 394 } 395 396 @Override 397 public void onDestroy() { 398 super.onDestroy(); 399 mAdapter.stopRequestProcessing(); 400 mAdapter.changeCursor(null); 401 getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver); 402 getActivity().getContentResolver().unregisterContentObserver(mContactsObserver); 403 getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver); 404 } 405 406 @Override 407 public void onSaveInstanceState(Bundle outState) { 408 super.onSaveInstanceState(outState); 409 outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter); 410 outState.putInt(KEY_LOG_LIMIT, mLogLimit); 411 outState.putLong(KEY_DATE_LIMIT, mDateLimit); 412 outState.putBoolean(KEY_SHOW_FOOTER, mHasFooterView); 413 outState.putBoolean(KEY_IS_REPORT_DIALOG_SHOWING, mIsReportDialogShowing); 414 outState.putString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber); 415 } 416 417 @Override 418 public void fetchCalls() { 419 mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit); 420 } 421 422 public void startCallsQuery() { 423 mAdapter.setLoading(true); 424 mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit); 425 } 426 427 private void startVoicemailStatusQuery() { 428 mCallLogQueryHandler.fetchVoicemailStatus(); 429 } 430 431 private void updateCallList(int filterType, long dateLimit) { 432 mCallLogQueryHandler.fetchCalls(filterType, dateLimit); 433 } 434 435 private void updateEmptyMessage(int filterType) { 436 final int messageId; 437 switch (filterType) { 438 case Calls.MISSED_TYPE: 439 messageId = R.string.recentMissed_empty; 440 break; 441 case Calls.VOICEMAIL_TYPE: 442 messageId = R.string.recentVoicemails_empty; 443 break; 444 case CallLogQueryHandler.CALL_TYPE_ALL: 445 messageId = R.string.recentCalls_empty; 446 break; 447 default: 448 throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: " 449 + filterType); 450 } 451 DialerUtils.configureEmptyListView( 452 getListView().getEmptyView(), R.drawable.empty_call_log, messageId, getResources()); 453 } 454 455 CallLogAdapter getAdapter() { 456 return mAdapter; 457 } 458 459 @Override 460 public void setMenuVisibility(boolean menuVisible) { 461 super.setMenuVisibility(menuVisible); 462 if (mMenuVisible != menuVisible) { 463 mMenuVisible = menuVisible; 464 if (!menuVisible) { 465 updateOnExit(); 466 } else if (isResumed()) { 467 refreshData(); 468 } 469 } 470 } 471 472 /** Requests updates to the data to be shown. */ 473 private void refreshData() { 474 // Prevent unnecessary refresh. 475 if (mRefreshDataRequired) { 476 // Mark all entries in the contact info cache as out of date, so they will be looked up 477 // again once being shown. 478 mAdapter.invalidateCache(); 479 startCallsQuery(); 480 startVoicemailStatusQuery(); 481 updateOnEntry(); 482 mRefreshDataRequired = false; 483 } 484 } 485 486 /** Updates call data and notification state while leaving the call log tab. */ 487 private void updateOnExit() { 488 updateOnTransition(false); 489 } 490 491 /** Updates call data and notification state while entering the call log tab. */ 492 private void updateOnEntry() { 493 updateOnTransition(true); 494 } 495 496 // TODO: Move to CallLogActivity 497 private void updateOnTransition(boolean onEntry) { 498 // We don't want to update any call data when keyguard is on because the user has likely not 499 // seen the new calls yet. 500 // This might be called before onCreate() and thus we need to check null explicitly. 501 if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) { 502 // On either of the transitions we update the missed call and voicemail notifications. 503 // While exiting we additionally consume all missed calls (by marking them as read). 504 mCallLogQueryHandler.markNewCallsAsOld(); 505 if (!onEntry) { 506 mCallLogQueryHandler.markMissedCallsAsRead(); 507 } 508 CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); 509 CallLogNotificationsHelper.updateVoicemailNotifications(getActivity()); 510 } 511 } 512 513 /** 514 * Enables/disables the showing of the view full call history footer 515 * 516 * @param hasFooterView Whether or not to show the footer 517 */ 518 public void setHasFooterView(boolean hasFooterView) { 519 mHasFooterView = hasFooterView; 520 maybeAddFooterView(); 521 } 522 523 /** 524 * Determine whether or not the footer view should be added to the listview. If getView() 525 * is null, which means onCreateView hasn't been called yet, defer the addition of the footer 526 * until onViewCreated has been called. 527 */ 528 private void maybeAddFooterView() { 529 if (!mHasFooterView || getView() == null) { 530 return; 531 } 532 533 if (mFooterView == null) { 534 mFooterView = getActivity().getLayoutInflater().inflate( 535 R.layout.recents_list_footer, (ViewGroup) getView(), false); 536 mFooterView.setOnClickListener(new OnClickListener() { 537 @Override 538 public void onClick(View v) { 539 ((HostInterface) getActivity()).showCallHistory(); 540 } 541 }); 542 } 543 544 final ListView listView = getListView(); 545 listView.removeFooterView(mFooterView); 546 listView.addFooterView(mFooterView); 547 548 ViewUtil.addBottomPaddingToListViewForFab(listView, getResources()); 549 } 550 551 @Override 552 public void onItemExpanded(final CallLogListItemView view) { 553 final int startingHeight = view.getHeight(); 554 final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag(); 555 final ViewTreeObserver observer = getListView().getViewTreeObserver(); 556 observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 557 @Override 558 public boolean onPreDraw() { 559 // We don't want to continue getting called for every draw. 560 if (observer.isAlive()) { 561 observer.removeOnPreDrawListener(this); 562 } 563 // Calculate some values to help with the animation. 564 final int endingHeight = view.getHeight(); 565 final int distance = Math.abs(endingHeight - startingHeight); 566 final int baseHeight = Math.min(endingHeight, startingHeight); 567 final boolean isExpand = endingHeight > startingHeight; 568 569 // Set the views back to the start state of the animation 570 view.getLayoutParams().height = startingHeight; 571 if (!isExpand) { 572 viewHolder.actionsView.setVisibility(View.VISIBLE); 573 } 574 CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, !isExpand); 575 576 // Set up the fade effect for the action buttons. 577 if (isExpand) { 578 // Start the fade in after the expansion has partly completed, otherwise it 579 // will be mostly over before the expansion completes. 580 viewHolder.actionsView.setAlpha(0f); 581 viewHolder.actionsView.animate() 582 .alpha(1f) 583 .setStartDelay(mFadeInStartDelay) 584 .setDuration(mFadeInDuration) 585 .start(); 586 } else { 587 viewHolder.actionsView.setAlpha(1f); 588 viewHolder.actionsView.animate() 589 .alpha(0f) 590 .setDuration(mFadeOutDuration) 591 .start(); 592 } 593 view.requestLayout(); 594 595 // Set up the animator to animate the expansion and shadow depth. 596 ValueAnimator animator = isExpand ? ValueAnimator.ofFloat(0f, 1f) 597 : ValueAnimator.ofFloat(1f, 0f); 598 599 // Figure out how much scrolling is needed to make the view fully visible. 600 final Rect localVisibleRect = new Rect(); 601 view.getLocalVisibleRect(localVisibleRect); 602 final int scrollingNeeded = localVisibleRect.top > 0 ? -localVisibleRect.top 603 : view.getMeasuredHeight() - localVisibleRect.height(); 604 final ListView listView = getListView(); 605 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 606 607 private int mCurrentScroll = 0; 608 609 @Override 610 public void onAnimationUpdate(ValueAnimator animator) { 611 Float value = (Float) animator.getAnimatedValue(); 612 613 // For each value from 0 to 1, animate the various parts of the layout. 614 view.getLayoutParams().height = (int) (value * distance + baseHeight); 615 float z = mExpandedItemTranslationZ * value; 616 viewHolder.callLogEntryView.setTranslationZ(z); 617 view.setTranslationZ(z); // WAR 618 view.requestLayout(); 619 620 if (isExpand) { 621 if (listView != null) { 622 int scrollBy = (int) (value * scrollingNeeded) - mCurrentScroll; 623 listView.smoothScrollBy(scrollBy, /* duration = */ 0); 624 mCurrentScroll += scrollBy; 625 } 626 } 627 } 628 }); 629 // Set everything to their final values when the animation's done. 630 animator.addListener(new AnimatorListenerAdapter() { 631 @Override 632 public void onAnimationEnd(Animator animation) { 633 view.getLayoutParams().height = LayoutParams.WRAP_CONTENT; 634 635 if (!isExpand) { 636 viewHolder.actionsView.setVisibility(View.GONE); 637 } else { 638 // This seems like it should be unnecessary, but without this, after 639 // navigating out of the activity and then back, the action view alpha 640 // is defaulting to the value (0) at the start of the expand animation. 641 viewHolder.actionsView.setAlpha(1); 642 } 643 CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, isExpand); 644 } 645 }); 646 647 animator.setDuration(mExpandCollapseDuration); 648 animator.start(); 649 650 // Return false so this draw does not occur to prevent the final frame from 651 // being drawn for the single frame before the animations start. 652 return false; 653 } 654 }); 655 } 656 657 /** 658 * Retrieves the call log view for the specified call Id. If the view is not currently 659 * visible, returns null. 660 * 661 * @param callId The call Id. 662 * @return The call log view. 663 */ 664 @Override 665 public CallLogListItemView getViewForCallId(long callId) { 666 ListView listView = getListView(); 667 668 int firstPosition = listView.getFirstVisiblePosition(); 669 int lastPosition = listView.getLastVisiblePosition(); 670 671 for (int position = 0; position <= lastPosition - firstPosition; position++) { 672 View view = listView.getChildAt(position); 673 674 if (view != null) { 675 final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag(); 676 if (viewHolder != null && viewHolder.rowId == callId) { 677 return (CallLogListItemView)view; 678 } 679 } 680 } 681 682 return null; 683 } 684 685 public void onBadDataReported(String number) { 686 mIsReportDialogShowing = false; 687 if (number == null) { 688 return; 689 } 690 mAdapter.onBadDataReported(number); 691 mAdapter.notifyDataSetChanged(); 692 } 693 694 public void onReportButtonClick(String number) { 695 DialogFragment df = ObjectFactory.getReportDialogFragment(number); 696 if (df != null) { 697 df.setTargetFragment(this, 0); 698 df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG); 699 mReportDialogNumber = number; 700 mIsReportDialogShowing = true; 701 } 702 } 703} 704