blob: 6ee24a11a24eb2bbc737f52c55e9cc485acc1b79 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc.
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mail.browse;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.ViewPager;
import android.view.ViewGroup;
import com.android.mail.R;
import com.android.mail.providers.Account;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import com.android.mail.ui.ConversationListCallbacks;
import com.android.mail.ui.ConversationViewFragment;
import com.android.mail.utils.FragmentStatePagerAdapter2;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
public class ConversationPagerAdapter extends FragmentStatePagerAdapter2 {
private final DataSetObserver mListObserver = new ListObserver();
private ConversationListCallbacks mListController;
private final Bundle mCommonFragmentArgs;
private final Conversation mInitialConversation;
/**
* In singleton mode, this adapter ignores the cursor contents and size, and acts as if the
* data set size is exactly size=1, with {@link #mInitialConversation} at position 0.
*/
private boolean mSingletonMode = true;
/**
* Adapter methods may trigger a data set change notification in the middle of a ViewPager
* update, but they are not safe to handle, so we have to ignore them. This will not ignore
* pager-external updates; it's impossible to be notified of an external change during
* an update.
*
* TODO: Queue up changes like this, if there ever are any that actually modify the data set.
* Right now there are none. Such a change would have to be of the form: instantiation or
* setPrimary somehow adds or removes items from the conversation cursor. Crazy!
*/
private boolean mSafeToNotify;
/**
* Need to keep this around to look up pager title strings.
*/
private Resources mResources;
/**
* This isn't great to create a circular dependency, but our usage of {@link #getPageTitle(int)}
* requires knowing which page is the currently visible to dynamically name offscreen pages
* "newer" and "older". And {@link #setPrimaryItem(ViewGroup, int, Object)} does not work well
* because it isn't updated as often as {@link ViewPager#getCurrentItem()} is.
* <p>
* We must be careful to null out this reference when the pager and adapter are decoupled to
* minimize dangling references.
*/
private ViewPager mPager;
private static final String LOG_TAG = new LogUtils().getLogTag();
public ConversationPagerAdapter(Resources res, FragmentManager fm, Account account,
Folder folder, Conversation initialConversation) {
super(fm, false /* enableSavedStates */);
mResources = res;
mCommonFragmentArgs = ConversationViewFragment.makeBasicArgs(account, folder);
mInitialConversation = initialConversation;
}
public void setSingletonMode(boolean enabled) {
if (mSingletonMode != enabled) {
mSingletonMode = enabled;
notifyDataSetChanged();
}
}
public boolean isSingletonMode() {
return mSingletonMode || getCursor() == null;
}
private Cursor getCursor() {
if (mListController == null) {
// Should never happen. It's the pager controller's responsibility to ensure the list
// controller reference is around at least as long as the pager is active and has this
// adapter.
LogUtils.wtf(LOG_TAG, new Error(), "Pager adapter has an unexpected null cursor");
}
return mListController.getConversationListCursor();
}
@Override
public Fragment getItem(int position) {
final Conversation c;
if (isSingletonMode()) {
// cursor-less adapter is a size-1 cursor that points to mInitialConversation.
// sanity-check
if (position != 0) {
LogUtils.wtf(LOG_TAG, "pager cursor is null and position is non-zero: %d",
position);
}
c = mInitialConversation;
c.position = 0;
} else {
final Cursor cursor = getCursor();
if (!cursor.moveToPosition(position)) {
LogUtils.wtf(LOG_TAG, "unable to seek to ConversationCursor pos=%d (%s)", position,
cursor);
return null;
}
// TODO: switch to something like MessageCursor or AttachmentCursor
// to re-use these models
c = new Conversation(cursor);
c.position = position;
}
final Fragment f = ConversationViewFragment.newInstance(mCommonFragmentArgs, c);
LogUtils.d(LOG_TAG, "IN PagerAdapter.getItem, frag=%s subj=%s", f, c.subject);
return f;
}
@Override
public int getCount() {
return (isSingletonMode()) ? 1 : getCursor().getCount();
}
@Override
public int getItemPosition(Object item) {
if (!(item instanceof ConversationViewFragment)) {
LogUtils.wtf(LOG_TAG, "getItemPosition received unexpected item: %s", item);
}
final ConversationViewFragment fragment = (ConversationViewFragment) item;
return getConversationPosition(fragment.getConversation());
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
LogUtils.d(LOG_TAG, "IN PagerAdapter.setPrimaryItem, pos=%d, frag=%s", position,
object);
super.setPrimaryItem(container, position, object);
}
@Override
public CharSequence getPageTitle(int position) {
final String title;
final int currentPosition = mPager.getCurrentItem();
if (position == currentPosition) {
title = mResources.getString(R.string.conversation_count, position + 1, getCount());
} else {
title = mResources.getString(position > currentPosition ?
R.string.conversation_newer : R.string.conversation_older);
}
return title;
}
@Override
public Parcelable saveState() {
LogUtils.d(LOG_TAG, "IN PagerAdapter.saveState");
return super.saveState();
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
LogUtils.d(LOG_TAG, "IN PagerAdapter.restoreState");
super.restoreState(state, loader);
}
@Override
public void startUpdate(ViewGroup container) {
mSafeToNotify = false;
super.startUpdate(container);
}
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
mSafeToNotify = true;
}
@Override
public void notifyDataSetChanged() {
if (!mSafeToNotify) {
LogUtils.d(LOG_TAG, "IN PagerAdapter.notifyDataSetChanged, ignoring unsafe update");
return;
}
super.notifyDataSetChanged();
}
@Override
public void setItemVisible(Fragment item, boolean visible) {
super.setItemVisible(item, visible);
final ConversationViewFragment fragment = (ConversationViewFragment) item;
fragment.setExtraUserVisibleHint(visible);
if (visible && mListController != null) {
final Conversation c = fragment.getConversation();
LogUtils.d(LOG_TAG, "pager adapter setting current conv: %s (%s)", c.subject, item);
mListController.setCurrentConversation(c);
}
}
public int getConversationPosition(Conversation conv) {
if (isSingletonMode()) {
if (conv != mInitialConversation) {
LogUtils.wtf(LOG_TAG, "unable to find conversation with null pager cursor. c=%s",
conv);
return POSITION_NONE;
}
return 0;
}
final Cursor cursor = getCursor();
final boolean networkWasEnabled = Utils.disableConversationCursorNetworkAccess(cursor);
int result = POSITION_NONE;
int pos = -1;
while (cursor.moveToPosition(++pos)) {
final long id = cursor.getLong(UIProvider.CONVERSATION_ID_COLUMN);
if (conv.id == id) {
LogUtils.d(LOG_TAG, "pager adapter found repositioned convo '%s' at pos=%d",
conv.subject, pos);
result = pos;
break;
}
}
if (networkWasEnabled) {
Utils.enableConversationCursorNetworkAccess(cursor);
}
return result;
}
public void setPager(ViewPager pager) {
mPager = pager;
}
public void setListController(ConversationListCallbacks listController) {
if (mListController != null) {
mListController.unregisterConversationListObserver(mListObserver);
}
mListController = listController;
if (mListController != null) {
mListController.registerConversationListObserver(mListObserver);
notifyDataSetChanged();
} else {
// We're being torn down; do not notify.
// Let the pager controller manage pager lifecycle.
}
}
// update the pager dataset as the Controller's cursor changes
private class ListObserver extends DataSetObserver {
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
}
}
}