1 package com.android.dialer.list; 2 3 import android.animation.LayoutTransition; 4 import android.app.ActionBar; 5 import android.app.Fragment; 6 import android.app.FragmentManager; 7 import android.content.Context; 8 import android.content.SharedPreferences; 9 import android.database.Cursor; 10 import android.os.Bundle; 11 import android.support.v13.app.FragmentPagerAdapter; 12 import android.support.v4.view.ViewPager; 13 import android.support.v4.view.ViewPager.OnPageChangeListener; 14 import android.util.Log; 15 import android.view.LayoutInflater; 16 import android.view.View; 17 import android.view.ViewGroup; 18 import android.widget.AbsListView; 19 import android.widget.ListView; 20 21 import com.android.contacts.common.GeoUtil; 22 import com.android.contacts.common.list.ViewPagerTabs; 23 import com.android.contacts.commonbind.analytics.AnalyticsUtil; 24 import com.android.dialer.DialtactsActivity; 25 import com.android.dialer.R; 26 import com.android.dialer.calllog.CallLogAdapter; 27 import com.android.dialer.calllog.CallLogFragment; 28 import com.android.dialer.calllog.CallLogQuery; 29 import com.android.dialer.calllog.CallLogQueryHandler; 30 import com.android.dialer.calllog.ContactInfoHelper; 31 import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard; 32 import com.android.dialer.util.DialerUtils; 33 import com.android.dialer.widget.ActionBarController; 34 import com.android.dialer.widget.OverlappingPaneLayout; 35 import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks; 36 import com.android.dialerbind.ObjectFactory; 37 38 import java.util.ArrayList; 39 40 /** 41 * Fragment that is used as the main screen of the Dialer. 42 * 43 * Contains a ViewPager that contains various contact lists like the Speed Dial list and the 44 * All Contacts list. This will also eventually contain the logic that allows sliding the 45 * ViewPager containing the lists up above the shortcut cards and pin it against the top of the 46 * screen. 47 */ 48 public class ListsFragment extends Fragment implements CallLogQueryHandler.Listener, 49 CallLogAdapter.CallFetcher, ViewPager.OnPageChangeListener { 50 51 private static final boolean DEBUG = DialtactsActivity.DEBUG; 52 private static final String TAG = "ListsFragment"; 53 54 public static final int TAB_INDEX_SPEED_DIAL = 0; 55 public static final int TAB_INDEX_RECENTS = 1; 56 public static final int TAB_INDEX_ALL_CONTACTS = 2; 57 58 public static final int TAB_INDEX_COUNT = 3; 59 60 private static final int MAX_RECENTS_ENTRIES = 20; 61 // Oldest recents entry to display is 2 weeks old. 62 private static final long OLDEST_RECENTS_DATE = 1000L * 60 * 60 * 24 * 14; 63 64 private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE = 65 "key_last_dismissed_call_shortcut_date"; 66 67 public static final float REMOVE_VIEW_SHOWN_ALPHA = 0.5f; 68 public static final float REMOVE_VIEW_HIDDEN_ALPHA = 1; 69 70 public interface HostInterface { showCallHistory()71 public void showCallHistory(); getActionBarController()72 public ActionBarController getActionBarController(); 73 } 74 75 private ActionBar mActionBar; 76 private ViewPager mViewPager; 77 private ViewPagerTabs mViewPagerTabs; 78 private ViewPagerAdapter mViewPagerAdapter; 79 private ListView mShortcutCardsListView; 80 private RemoveView mRemoveView; 81 private View mRemoveViewContent; 82 private SpeedDialFragment mSpeedDialFragment; 83 private CallLogFragment mRecentsFragment; 84 private AllContactsFragment mAllContactsFragment; 85 private ArrayList<OnPageChangeListener> mOnPageChangeListeners = 86 new ArrayList<OnPageChangeListener>(); 87 88 private String[] mTabTitles; 89 90 private ShortcutCardsAdapter mMergedAdapter; 91 private CallLogAdapter mCallLogAdapter; 92 private CallLogQueryHandler mCallLogQueryHandler; 93 private OverlappingPaneLayout mOverlappingPaneLayout; 94 95 private boolean mIsPanelOpen = true; 96 97 /** 98 * Call shortcuts older than this date (persisted in shared preferences) will not show up in 99 * at the top of the screen 100 */ 101 private long mLastCallShortcutDate = 0; 102 103 /** 104 * The date of the current call shortcut that is showing on screen. 105 */ 106 private long mCurrentCallShortcutDate = 0; 107 108 private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() { 109 @Override 110 public void onPanelSlide(View panel, float slideOffset) { 111 // For every 1 percent that the panel is slid upwards, clip 1 percent off the top 112 // edge of the shortcut card, to achieve the animated effect of the shortcut card 113 // being pushed out of view when the panel is slid upwards. slideOffset is 1 when 114 // the shortcut card is fully exposed, and 0 when completely hidden. 115 float ratioCardHidden = (1 - slideOffset); 116 if (mShortcutCardsListView.getChildCount() > 0) { 117 final SwipeableShortcutCard v = 118 (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0); 119 v.clipCard(ratioCardHidden); 120 } 121 122 if (mActionBar != null) { 123 // Amount of available space that is not being hidden by the bottom pane 124 final int topPaneHeight = (int) (slideOffset * mShortcutCardsListView.getHeight()); 125 126 final int availableActionBarHeight = 127 Math.min(mActionBar.getHeight(), topPaneHeight); 128 final ActionBarController controller = 129 ((HostInterface) getActivity()).getActionBarController(); 130 controller.setHideOffset(mActionBar.getHeight() - availableActionBarHeight); 131 132 if (!mActionBar.isShowing()) { 133 mActionBar.show(); 134 } 135 } 136 } 137 138 @Override 139 public void onPanelOpened(View panel) { 140 if (DEBUG) { 141 Log.d(TAG, "onPanelOpened"); 142 } 143 mIsPanelOpen = true; 144 } 145 146 @Override 147 public void onPanelClosed(View panel) { 148 if (DEBUG) { 149 Log.d(TAG, "onPanelClosed"); 150 } 151 mIsPanelOpen = false; 152 } 153 154 @Override 155 public void onPanelFlingReachesEdge(int velocityY) { 156 if (getCurrentListView() != null) { 157 getCurrentListView().fling(velocityY); 158 } 159 } 160 161 @Override 162 public boolean isScrollableChildUnscrolled() { 163 final AbsListView listView = getCurrentListView(); 164 return listView != null && (listView.getChildCount() == 0 165 || listView.getChildAt(0).getTop() == listView.getPaddingTop()); 166 } 167 }; 168 getCurrentListView()169 private AbsListView getCurrentListView() { 170 final int position = mViewPager.getCurrentItem(); 171 switch (getRtlPosition(position)) { 172 case TAB_INDEX_SPEED_DIAL: 173 return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView(); 174 case TAB_INDEX_RECENTS: 175 return mRecentsFragment == null ? null : mRecentsFragment.getListView(); 176 case TAB_INDEX_ALL_CONTACTS: 177 return mAllContactsFragment == null ? null : mAllContactsFragment.getListView(); 178 } 179 throw new IllegalStateException("No fragment at position " + position); 180 } 181 182 public class ViewPagerAdapter extends FragmentPagerAdapter { ViewPagerAdapter(FragmentManager fm)183 public ViewPagerAdapter(FragmentManager fm) { 184 super(fm); 185 } 186 187 @Override getItemId(int position)188 public long getItemId(int position) { 189 return getRtlPosition(position); 190 } 191 192 @Override getItem(int position)193 public Fragment getItem(int position) { 194 switch (getRtlPosition(position)) { 195 case TAB_INDEX_SPEED_DIAL: 196 mSpeedDialFragment = new SpeedDialFragment(); 197 return mSpeedDialFragment; 198 case TAB_INDEX_RECENTS: 199 mRecentsFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL, 200 MAX_RECENTS_ENTRIES, System.currentTimeMillis() - OLDEST_RECENTS_DATE); 201 mRecentsFragment.setHasFooterView(true); 202 return mRecentsFragment; 203 case TAB_INDEX_ALL_CONTACTS: 204 mAllContactsFragment = new AllContactsFragment(); 205 return mAllContactsFragment; 206 } 207 throw new IllegalStateException("No fragment at position " + position); 208 } 209 210 @Override instantiateItem(ViewGroup container, int position)211 public Object instantiateItem(ViewGroup container, int position) { 212 // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called. 213 // Copy the fragments that the FragmentManager finds so that we can store them in 214 // instance variables for later. 215 final Fragment fragment = 216 (Fragment) super.instantiateItem(container, position); 217 if (fragment instanceof SpeedDialFragment) { 218 mSpeedDialFragment = (SpeedDialFragment) fragment; 219 } else if (fragment instanceof CallLogFragment) { 220 mRecentsFragment = (CallLogFragment) fragment; 221 } else if (fragment instanceof AllContactsFragment) { 222 mAllContactsFragment = (AllContactsFragment) fragment; 223 } 224 return fragment; 225 } 226 227 @Override getCount()228 public int getCount() { 229 return TAB_INDEX_COUNT; 230 } 231 232 @Override getPageTitle(int position)233 public CharSequence getPageTitle(int position) { 234 return mTabTitles[position]; 235 } 236 } 237 238 @Override onCreate(Bundle savedInstanceState)239 public void onCreate(Bundle savedInstanceState) { 240 super.onCreate(savedInstanceState); 241 242 mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), 243 this, 1); 244 final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); 245 mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, 246 new ContactInfoHelper(getActivity(), currentCountryIso), null, null, false); 247 248 mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter); 249 } 250 251 @Override onResume()252 public void onResume() { 253 super.onResume(); 254 final SharedPreferences prefs = getActivity().getSharedPreferences( 255 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 256 mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0); 257 mActionBar = getActivity().getActionBar(); 258 fetchCalls(); 259 mCallLogAdapter.setLoading(true); 260 if (getUserVisibleHint()) { 261 sendScreenViewForPosition(mViewPager.getCurrentItem()); 262 } 263 } 264 265 @Override onPause()266 public void onPause() { 267 // Wipe the cache to refresh the call shortcut item. This is not that expensive because 268 // it only contains one item. 269 mCallLogAdapter.invalidateCache(); 270 super.onPause(); 271 } 272 273 @Override onDestroy()274 public void onDestroy() { 275 mCallLogAdapter.stopRequestProcessing(); 276 super.onDestroy(); 277 } 278 279 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)280 public View onCreateView(LayoutInflater inflater, ViewGroup container, 281 Bundle savedInstanceState) { 282 final View parentView = inflater.inflate(R.layout.lists_fragment, container, false); 283 mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager); 284 mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager()); 285 mViewPager.setAdapter(mViewPagerAdapter); 286 mViewPager.setOffscreenPageLimit(2); 287 mViewPager.setOnPageChangeListener(this); 288 mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_SPEED_DIAL)); 289 290 mTabTitles = new String[TAB_INDEX_COUNT]; 291 mTabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial); 292 mTabTitles[TAB_INDEX_RECENTS] = getResources().getString(R.string.tab_recents); 293 mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts); 294 295 mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header); 296 mViewPagerTabs.setViewPager(mViewPager); 297 addOnPageChangeListener(mViewPagerTabs); 298 299 mShortcutCardsListView = (ListView) parentView.findViewById(R.id.shortcut_card_list); 300 mShortcutCardsListView.setAdapter(mMergedAdapter); 301 302 mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view); 303 mRemoveViewContent = parentView.findViewById(R.id.remove_view_content); 304 305 setupPaneLayout((OverlappingPaneLayout) parentView); 306 mOverlappingPaneLayout = (OverlappingPaneLayout) parentView; 307 308 return parentView; 309 } 310 311 @Override onVoicemailStatusFetched(Cursor statusCursor)312 public void onVoicemailStatusFetched(Cursor statusCursor) { 313 // no-op 314 } 315 316 @Override onCallsFetched(Cursor cursor)317 public boolean onCallsFetched(Cursor cursor) { 318 mCallLogAdapter.setLoading(false); 319 320 // Save the date of the most recent call log item 321 if (cursor != null && cursor.moveToFirst()) { 322 mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE); 323 } 324 325 mCallLogAdapter.changeCursor(cursor); 326 mMergedAdapter.notifyDataSetChanged(); 327 328 // Refresh the overlapping pane to ensure that any changes in the shortcut card height 329 // are appropriately reflected in the overlap position. 330 mOverlappingPaneLayout.refresh(); 331 332 // Return true; took ownership of cursor 333 return true; 334 } 335 336 @Override fetchCalls()337 public void fetchCalls() { 338 mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate); 339 } 340 dismissShortcut(View view)341 public void dismissShortcut(View view) { 342 mLastCallShortcutDate = mCurrentCallShortcutDate; 343 final SharedPreferences prefs = view.getContext().getSharedPreferences( 344 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 345 prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate) 346 .apply(); 347 fetchCalls(); 348 } 349 addOnPageChangeListener(OnPageChangeListener onPageChangeListener)350 public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) { 351 if (!mOnPageChangeListeners.contains(onPageChangeListener)) { 352 mOnPageChangeListeners.add(onPageChangeListener); 353 } 354 } 355 356 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)357 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 358 final int count = mOnPageChangeListeners.size(); 359 for (int i = 0; i < count; i++) { 360 mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset, 361 positionOffsetPixels); 362 } 363 } 364 365 @Override onPageSelected(int position)366 public void onPageSelected(int position) { 367 final int count = mOnPageChangeListeners.size(); 368 for (int i = 0; i < count; i++) { 369 mOnPageChangeListeners.get(i).onPageSelected(position); 370 } 371 sendScreenViewForPosition(position); 372 } 373 374 @Override onPageScrollStateChanged(int state)375 public void onPageScrollStateChanged(int state) { 376 final int count = mOnPageChangeListeners.size(); 377 for (int i = 0; i < count; i++) { 378 mOnPageChangeListeners.get(i).onPageScrollStateChanged(state); 379 } 380 } 381 showRemoveView(boolean show)382 public void showRemoveView(boolean show) { 383 mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE); 384 mRemoveView.setAlpha(show ? 0 : 1); 385 mRemoveView.animate().alpha(show ? 1 : 0).start(); 386 387 if (mShortcutCardsListView.getChildCount() > 0) { 388 View v = mShortcutCardsListView.getChildAt(0); 389 v.animate().withLayer() 390 .alpha(show ? REMOVE_VIEW_SHOWN_ALPHA : REMOVE_VIEW_HIDDEN_ALPHA) 391 .start(); 392 } 393 } 394 shouldShowActionBar()395 public boolean shouldShowActionBar() { 396 return mIsPanelOpen && mActionBar != null; 397 } 398 isPaneOpen()399 public boolean isPaneOpen() { 400 return mIsPanelOpen; 401 } 402 setupPaneLayout(OverlappingPaneLayout paneLayout)403 private void setupPaneLayout(OverlappingPaneLayout paneLayout) { 404 // TODO: Remove the notion of a capturable view. The entire view be slideable, once 405 // the framework better supports nested scrolling. 406 paneLayout.setCapturableView(mViewPagerTabs); 407 paneLayout.openPane(); 408 paneLayout.setPanelSlideCallbacks(mPanelSlideCallbacks); 409 paneLayout.setIntermediatePinnedOffset( 410 ((HostInterface) getActivity()).getActionBarController().getActionBarHeight()); 411 412 LayoutTransition transition = paneLayout.getLayoutTransition(); 413 // Turns on animations for all types of layout changes so that they occur for 414 // height changes. 415 transition.enableTransitionType(LayoutTransition.CHANGING); 416 } 417 getSpeedDialFragment()418 public SpeedDialFragment getSpeedDialFragment() { 419 return mSpeedDialFragment; 420 } 421 getRemoveView()422 public RemoveView getRemoveView() { 423 return mRemoveView; 424 } 425 getRtlPosition(int position)426 public int getRtlPosition(int position) { 427 if (DialerUtils.isRtl()) { 428 return TAB_INDEX_COUNT - 1 - position; 429 } 430 return position; 431 } 432 sendScreenViewForCurrentPosition()433 public void sendScreenViewForCurrentPosition() { 434 sendScreenViewForPosition(mViewPager.getCurrentItem()); 435 } 436 sendScreenViewForPosition(int position)437 private void sendScreenViewForPosition(int position) { 438 if (!isResumed()) { 439 return; 440 } 441 String fragmentName; 442 switch (getRtlPosition(position)) { 443 case TAB_INDEX_SPEED_DIAL: 444 fragmentName = SpeedDialFragment.class.getSimpleName(); 445 break; 446 case TAB_INDEX_RECENTS: 447 fragmentName = CallLogFragment.class.getSimpleName(); 448 break; 449 case TAB_INDEX_ALL_CONTACTS: 450 fragmentName = AllContactsFragment.class.getSimpleName(); 451 break; 452 default: 453 return; 454 } 455 AnalyticsUtil.sendScreenView(fragmentName, getActivity(), null); 456 } 457 } 458