1package com.android.dialer.list; 2 3import android.animation.LayoutTransition; 4import android.app.ActionBar; 5import android.app.Fragment; 6import android.app.FragmentManager; 7import android.content.Context; 8import android.content.SharedPreferences; 9import android.database.Cursor; 10import android.os.Bundle; 11import android.support.v13.app.FragmentPagerAdapter; 12import android.support.v4.view.ViewPager; 13import android.support.v4.view.ViewPager.OnPageChangeListener; 14import android.util.Log; 15import android.view.LayoutInflater; 16import android.view.View; 17import android.view.ViewGroup; 18import android.widget.AbsListView; 19import android.widget.ListView; 20 21import com.android.contacts.common.GeoUtil; 22import com.android.contacts.common.list.ViewPagerTabs; 23import com.android.dialer.DialtactsActivity; 24import com.android.dialer.R; 25import com.android.dialer.calllog.CallLogAdapter; 26import com.android.dialer.calllog.CallLogFragment; 27import com.android.dialer.calllog.CallLogQuery; 28import com.android.dialer.calllog.CallLogQueryHandler; 29import com.android.dialer.calllog.ContactInfoHelper; 30import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard; 31import com.android.dialer.util.DialerUtils; 32import com.android.dialer.widget.OverlappingPaneLayout; 33import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks; 34import com.android.dialerbind.analytics.AnalyticsFragment; 35import com.android.dialerbind.ObjectFactory; 36 37import java.util.ArrayList; 38 39/** 40 * Fragment that is used as the main screen of the Dialer. 41 * 42 * Contains a ViewPager that contains various contact lists like the Speed Dial list and the 43 * All Contacts list. This will also eventually contain the logic that allows sliding the 44 * ViewPager containing the lists up above the shortcut cards and pin it against the top of the 45 * screen. 46 */ 47public class ListsFragment extends AnalyticsFragment implements CallLogQueryHandler.Listener, 48 CallLogAdapter.CallFetcher, ViewPager.OnPageChangeListener { 49 50 private static final boolean DEBUG = DialtactsActivity.DEBUG; 51 private static final String TAG = "ListsFragment"; 52 53 public static final int TAB_INDEX_SPEED_DIAL = 0; 54 public static final int TAB_INDEX_RECENTS = 1; 55 public static final int TAB_INDEX_ALL_CONTACTS = 2; 56 57 public static final int TAB_INDEX_COUNT = 3; 58 59 private static final int MAX_RECENTS_ENTRIES = 20; 60 // Oldest recents entry to display is 2 weeks old. 61 private static final long OLDEST_RECENTS_DATE = 1000L * 60 * 60 * 24 * 14; 62 63 private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE = 64 "key_last_dismissed_call_shortcut_date"; 65 66 public static final float REMOVE_VIEW_SHOWN_ALPHA = 0.5f; 67 public static final float REMOVE_VIEW_HIDDEN_ALPHA = 1; 68 69 public interface HostInterface { 70 public void showCallHistory(); 71 public int getActionBarHeight(); 72 public void setActionBarHideOffset(int offset); 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 94 private boolean mIsPanelOpen = true; 95 96 /** 97 * Call shortcuts older than this date (persisted in shared preferences) will not show up in 98 * at the top of the screen 99 */ 100 private long mLastCallShortcutDate = 0; 101 102 /** 103 * The date of the current call shortcut that is showing on screen. 104 */ 105 private long mCurrentCallShortcutDate = 0; 106 107 private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() { 108 @Override 109 public void onPanelSlide(View panel, float slideOffset) { 110 // For every 1 percent that the panel is slid upwards, clip 1 percent off the top 111 // edge of the shortcut card, to achieve the animated effect of the shortcut card 112 // being pushed out of view when the panel is slid upwards. slideOffset is 1 when 113 // the shortcut card is fully exposed, and 0 when completely hidden. 114 float ratioCardHidden = (1 - slideOffset); 115 if (mShortcutCardsListView.getChildCount() > 0) { 116 final SwipeableShortcutCard v = 117 (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0); 118 v.clipCard(ratioCardHidden); 119 } 120 121 if (mActionBar != null) { 122 // Amount of available space that is not being hidden by the bottom pane 123 final int topPaneHeight = (int) (slideOffset * mShortcutCardsListView.getHeight()); 124 125 final int availableActionBarHeight = 126 Math.min(mActionBar.getHeight(), topPaneHeight); 127 ((HostInterface) getActivity()).setActionBarHideOffset( 128 mActionBar.getHeight() - availableActionBarHeight); 129 130 if (!mActionBar.isShowing()) { 131 mActionBar.show(); 132 } 133 } 134 } 135 136 @Override 137 public void onPanelOpened(View panel) { 138 if (DEBUG) { 139 Log.d(TAG, "onPanelOpened"); 140 } 141 mIsPanelOpen = true; 142 } 143 144 @Override 145 public void onPanelClosed(View panel) { 146 if (DEBUG) { 147 Log.d(TAG, "onPanelClosed"); 148 } 149 mIsPanelOpen = false; 150 } 151 152 @Override 153 public void onPanelFlingReachesEdge(int velocityY) { 154 if (getCurrentListView() != null) { 155 getCurrentListView().fling(velocityY); 156 } 157 } 158 159 @Override 160 public boolean isScrollableChildUnscrolled() { 161 final AbsListView listView = getCurrentListView(); 162 return listView != null && (listView.getChildCount() == 0 163 || listView.getChildAt(0).getTop() == listView.getPaddingTop()); 164 } 165 }; 166 167 private AbsListView getCurrentListView() { 168 final int position = mViewPager.getCurrentItem(); 169 switch (getRtlPosition(position)) { 170 case TAB_INDEX_SPEED_DIAL: 171 return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView(); 172 case TAB_INDEX_RECENTS: 173 return mRecentsFragment == null ? null : mRecentsFragment.getListView(); 174 case TAB_INDEX_ALL_CONTACTS: 175 return mAllContactsFragment == null ? null : mAllContactsFragment.getListView(); 176 } 177 throw new IllegalStateException("No fragment at position " + position); 178 } 179 180 public class ViewPagerAdapter extends FragmentPagerAdapter { 181 public ViewPagerAdapter(FragmentManager fm) { 182 super(fm); 183 } 184 185 @Override 186 public long getItemId(int position) { 187 return getRtlPosition(position); 188 } 189 190 @Override 191 public Fragment getItem(int position) { 192 switch (getRtlPosition(position)) { 193 case TAB_INDEX_SPEED_DIAL: 194 mSpeedDialFragment = new SpeedDialFragment(); 195 return mSpeedDialFragment; 196 case TAB_INDEX_RECENTS: 197 mRecentsFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL, 198 MAX_RECENTS_ENTRIES, System.currentTimeMillis() - OLDEST_RECENTS_DATE); 199 mRecentsFragment.setHasFooterView(true); 200 return mRecentsFragment; 201 case TAB_INDEX_ALL_CONTACTS: 202 mAllContactsFragment = new AllContactsFragment(); 203 return mAllContactsFragment; 204 } 205 throw new IllegalStateException("No fragment at position " + position); 206 } 207 208 @Override 209 public Object instantiateItem(ViewGroup container, int position) { 210 // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called. 211 // Copy the fragments that the FragmentManager finds so that we can store them in 212 // instance variables for later. 213 final Fragment fragment = 214 (Fragment) super.instantiateItem(container, position); 215 if (fragment instanceof SpeedDialFragment) { 216 mSpeedDialFragment = (SpeedDialFragment) fragment; 217 } else if (fragment instanceof CallLogFragment) { 218 mRecentsFragment = (CallLogFragment) fragment; 219 } else if (fragment instanceof AllContactsFragment) { 220 mAllContactsFragment = (AllContactsFragment) fragment; 221 } 222 return fragment; 223 } 224 225 @Override 226 public int getCount() { 227 return TAB_INDEX_COUNT; 228 } 229 230 @Override 231 public CharSequence getPageTitle(int position) { 232 return mTabTitles[position]; 233 } 234 } 235 236 @Override 237 public void onCreate(Bundle savedInstanceState) { 238 super.onCreate(savedInstanceState); 239 240 mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), 241 this, 1); 242 final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); 243 mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, 244 new ContactInfoHelper(getActivity(), currentCountryIso), null, null, false); 245 246 mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter); 247 } 248 249 @Override 250 public void onStart() { 251 super.onStart(); 252 } 253 254 @Override 255 public void onResume() { 256 super.onResume(); 257 final SharedPreferences prefs = getActivity().getSharedPreferences( 258 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 259 mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0); 260 mActionBar = getActivity().getActionBar(); 261 fetchCalls(); 262 mCallLogAdapter.setLoading(true); 263 } 264 265 @Override 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 274 public void onDestroy() { 275 mCallLogAdapter.stopRequestProcessing(); 276 super.onDestroy(); 277 } 278 279 @Override 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 307 return parentView; 308 } 309 310 @Override 311 public void onVoicemailStatusFetched(Cursor statusCursor) { 312 // no-op 313 } 314 315 @Override 316 public boolean onCallsFetched(Cursor cursor) { 317 mCallLogAdapter.setLoading(false); 318 319 // Save the date of the most recent call log item 320 if (cursor != null && cursor.moveToFirst()) { 321 mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE); 322 } 323 324 mCallLogAdapter.changeCursor(cursor); 325 mMergedAdapter.notifyDataSetChanged(); 326 // Return true; took ownership of cursor 327 return true; 328 } 329 330 @Override 331 public void fetchCalls() { 332 mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate); 333 } 334 335 public void dismissShortcut(View view) { 336 mLastCallShortcutDate = mCurrentCallShortcutDate; 337 final SharedPreferences prefs = view.getContext().getSharedPreferences( 338 DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE); 339 prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate) 340 .apply(); 341 fetchCalls(); 342 } 343 344 public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) { 345 if (!mOnPageChangeListeners.contains(onPageChangeListener)) { 346 mOnPageChangeListeners.add(onPageChangeListener); 347 } 348 } 349 350 @Override 351 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 352 final int count = mOnPageChangeListeners.size(); 353 for (int i = 0; i < count; i++) { 354 mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset, 355 positionOffsetPixels); 356 } 357 } 358 359 @Override 360 public void onPageSelected(int position) { 361 if (position == TAB_INDEX_SPEED_DIAL && mSpeedDialFragment != null) { 362 mSpeedDialFragment.sendScreenView(); 363 } else if (position == TAB_INDEX_RECENTS && mRecentsFragment != null) { 364 mRecentsFragment.sendScreenView(); 365 } else if (position == TAB_INDEX_ALL_CONTACTS && mAllContactsFragment != null) { 366 mAllContactsFragment.sendScreenView(); 367 } 368 final int count = mOnPageChangeListeners.size(); 369 for (int i = 0; i < count; i++) { 370 mOnPageChangeListeners.get(i).onPageSelected(position); 371 } 372 } 373 374 @Override 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 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 395 public boolean shouldShowActionBar() { 396 return mIsPanelOpen && mActionBar != null; 397 } 398 399 public boolean isPaneOpen() { 400 return mIsPanelOpen; 401 } 402 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()).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 418 public SpeedDialFragment getSpeedDialFragment() { 419 return mSpeedDialFragment; 420 } 421 422 public RemoveView getRemoveView() { 423 return mRemoveView; 424 } 425 426 public int getRtlPosition(int position) { 427 if (DialerUtils.isRtl()) { 428 return TAB_INDEX_COUNT - 1 - position; 429 } 430 return position; 431 } 432} 433