ConversationPagerController.java revision 68459a7b3265da5002ff331529f12f6b408c9b03
1/* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mail.browse; 19 20import android.app.Fragment; 21import android.app.FragmentManager; 22import android.support.v4.view.ViewPager; 23import android.support.v4.view.ViewPager.OnPageChangeListener; 24import android.view.View; 25 26import com.android.mail.R; 27import com.android.mail.providers.Account; 28import com.android.mail.providers.Conversation; 29import com.android.mail.providers.Folder; 30import com.android.mail.ui.AbstractActivityController; 31import com.android.mail.ui.ActivityController; 32import com.android.mail.ui.RestrictedActivity; 33import com.android.mail.ui.SubjectDisplayChanger; 34import com.android.mail.utils.LogTag; 35import com.android.mail.utils.LogUtils; 36 37/** 38 * A simple controller for a {@link ViewPager} of conversations. 39 * <p> 40 * Instead of placing a ViewPager in a Fragment that replaces the other app views, we leave a 41 * ViewPager in the activity's view hierarchy at all times and have this controller manage it. 42 * This allows the ViewPager to safely instantiate inner conversation fragments since it is not 43 * itself contained in a Fragment (no nested fragments!). 44 * <p> 45 * This arrangement has pros and cons...<br> 46 * pros: FragmentManager manages restoring conversation fragments, each conversation gets its own 47 * LoaderManager<br> 48 * cons: the activity's Controller has to specially handle show/hide conversation view, 49 * conversation fragment transitions must be done manually 50 * <p> 51 * This controller is a small delegate of {@link AbstractActivityController} and shares its 52 * lifetime. 53 * 54 */ 55public class ConversationPagerController implements OnPageChangeListener { 56 57 private ViewPager mPager; 58 private ConversationPagerAdapter mPagerAdapter; 59 private FragmentManager mFragmentManager; 60 private ActivityController mActivityController; 61 private SubjectDisplayChanger mSubjectDisplayChanger; 62 private boolean mShown; 63 64 private static final String LOG_TAG = LogTag.getLogTag(); 65 66 /** 67 * Enables an optimization to the PagerAdapter that causes ViewPager to initially load just the 68 * target conversation, then when the conversation view signals that the conversation is loaded 69 * and visible (via onConversationSeen), we switch to paged mode to load the left/right 70 * adjacent conversations. 71 * <p> 72 * Should improve load times. It also works around an issue in ViewPager that always loads item 73 * zero (with the fragment visibility hint ON) when the adapter is initially set. 74 */ 75 private static final boolean ENABLE_SINGLETON_INITIAL_LOAD = true; 76 77 public ConversationPagerController(RestrictedActivity activity, 78 ActivityController controller) { 79 mFragmentManager = activity.getFragmentManager(); 80 mPager = (ViewPager) activity.findViewById(R.id.conversation_pane); 81 mActivityController = controller; 82 mSubjectDisplayChanger = controller.getSubjectDisplayChanger(); 83 } 84 85 public void show(Account account, Folder folder, Conversation initialConversation) { 86 if (mShown) { 87 LogUtils.d(LOG_TAG, "IN CPC.show, but already shown"); 88 // optimize for the case where account+folder are the same, when we can just shift 89 // the existing pager to show the new conversation 90 if (mPagerAdapter != null && mPagerAdapter.matches(account, folder)) { 91 final int pos = mPagerAdapter.getConversationPosition(initialConversation); 92 if (pos >= 0) { 93 mPager.setCurrentItem(pos); 94 return; 95 } 96 } 97 // unable to shift, destroy existing state and fall through to normal startup 98 cleanup(); 99 } 100 101 mPager.setVisibility(View.VISIBLE); 102 103 mPagerAdapter = new ConversationPagerAdapter(mPager.getResources(), mFragmentManager, 104 account, folder, initialConversation); 105 mPagerAdapter.setSingletonMode(ENABLE_SINGLETON_INITIAL_LOAD); 106 mPagerAdapter.setActivityController(mActivityController); 107 mPagerAdapter.setPager(mPager); 108 LogUtils.d(LOG_TAG, "IN CPC.show, adapter=%s", mPagerAdapter); 109 110 mPager.setOnPageChangeListener(this); 111 112 LogUtils.d(LOG_TAG, "init pager adapter, count=%d initial=%s", mPagerAdapter.getCount(), 113 initialConversation.subject); 114 mPager.setAdapter(mPagerAdapter); 115 116 if (!ENABLE_SINGLETON_INITIAL_LOAD) { 117 // FIXME: unnecessary to do this on restore. setAdapter will restore current position 118 final int initialPos = mPagerAdapter.getConversationPosition(initialConversation); 119 LogUtils.w(LOG_TAG, "*** pager fragment init pos=%d", initialPos); 120 mPager.setCurrentItem(initialPos); 121 } 122 123 mShown = true; 124 } 125 126 public void hide() { 127 if (!mShown) { 128 LogUtils.d(LOG_TAG, "IN CPC.hide, but already hidden"); 129 return; 130 } 131 mShown = false; 132 mPager.setVisibility(View.GONE); 133 134 mSubjectDisplayChanger.clearSubject(); 135 136 LogUtils.d(LOG_TAG, "IN CPC.hide, clearing adapter and unregistering list observer"); 137 mPager.setAdapter(null); 138 mPager.setOnPageChangeListener(null); 139 cleanup(); 140 } 141 142 public void onDestroy() { 143 // need to release resources before a configuration change kills the activity and controller 144 cleanup(); 145 } 146 147 private void cleanup() { 148 if (mPagerAdapter != null) { 149 // stop observing the conversation list 150 mPagerAdapter.setActivityController(null); 151 mPagerAdapter.setPager(null); 152 mPagerAdapter = null; 153 } 154 } 155 156 public void onConversationSeen(Conversation conv) { 157 // take the adapter out of singleton mode to begin loading the 158 // other non-visible conversations 159 if (mPagerAdapter != null && mPagerAdapter.isSingletonMode()) { 160 LogUtils.d(LOG_TAG, "IN pager adapter, finished loading primary conversation," + 161 " switching to cursor mode to load other conversations"); 162 mPagerAdapter.setSingletonMode(false); 163 } 164 } 165 166 @Override 167 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 168 // no-op 169 } 170 171 @Override 172 public void onPageSelected(int position) { 173 final Fragment f = mPagerAdapter.getFragmentAt(position); 174 if (f != null) { 175 mPagerAdapter.setItemVisible(f, true); 176 } 177 } 178 179 @Override 180 public void onPageScrollStateChanged(int state) { 181 // no-op 182 } 183 184 /** 185 * Stops listening to changes to the adapter. This must be followed immediately by 186 * {@link #hide()}. 187 */ 188 public void stopListening() { 189 mPagerAdapter.setActivityController(null); 190 } 191} 192