blob: 27b04b99e7e74e6723aa1d22d2f4cc28e82fe0df [file] [log] [blame]
/*
* Copyright (C) 2012 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.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.Loader.OnLoadCompleteListener;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Looper;
import android.support.v4.app.TaskStackBuilder;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.CharacterStyle;
import android.text.style.TextAppearanceSpan;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import com.android.mail.R;
import com.android.mail.browse.SendersView;
import com.android.mail.compose.ComposeActivity;
import com.android.mail.preferences.MailPrefs;
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.providers.UIProvider.ConversationListQueryParameters;
import com.android.mail.providers.UIProvider.FolderType;
import com.android.mail.utils.AccountUtils;
import com.android.mail.utils.FolderUri;
import com.android.mail.utils.DelayedTaskHandler;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
import java.util.ArrayList;
public class WidgetService extends RemoteViewsService {
/**
* Lock to avoid race condition between widgets.
*/
private static final Object sWidgetLock = new Object();
private static final String LOG_TAG = LogTag.getLogTag();
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new MailFactory(getApplicationContext(), intent, this);
}
protected void configureValidAccountWidget(Context context, RemoteViews remoteViews,
int appWidgetId, Account account, final int folderType, final Uri folderUri,
final Uri folderConversationListUri, String folderName) {
configureValidAccountWidget(context, remoteViews, appWidgetId, account, folderType,
folderUri, folderConversationListUri, folderName, WidgetService.class);
}
/**
* Modifies the remoteView for the given account and folder.
*/
public static void configureValidAccountWidget(Context context, RemoteViews remoteViews,
int appWidgetId, Account account, final int folderType, final Uri folderUri,
final Uri folderConversationListUri, String folderDisplayName, Class<?> widgetService) {
remoteViews.setViewVisibility(R.id.widget_folder, View.VISIBLE);
// If the folder or account name are empty, we don't want to overwrite the valid data that
// had been saved previously. Since the launcher will save the state of the remote views
// we should rely on the fact that valid data has been saved. But we should still log this,
// as it shouldn't happen
if (TextUtils.isEmpty(folderDisplayName) || TextUtils.isEmpty(account.name)) {
LogUtils.e(LOG_TAG, new Error(),
"Empty folder or account name. account: %s, folder: %s",
account.name, folderDisplayName);
}
if (!TextUtils.isEmpty(folderDisplayName)) {
remoteViews.setTextViewText(R.id.widget_folder, folderDisplayName);
}
remoteViews.setViewVisibility(R.id.widget_account_noflip, View.VISIBLE);
if (!TextUtils.isEmpty(account.name)) {
remoteViews.setTextViewText(R.id.widget_account_noflip, account.name);
remoteViews.setTextViewText(R.id.widget_account, account.name);
}
remoteViews.setViewVisibility(R.id.widget_account_unread_flipper, View.GONE);
remoteViews.setViewVisibility(R.id.widget_compose, View.VISIBLE);
remoteViews.setViewVisibility(R.id.conversation_list, View.VISIBLE);
remoteViews.setViewVisibility(R.id.empty_conversation_list, View.VISIBLE);
remoteViews.setViewVisibility(R.id.widget_folder_not_synced, View.GONE);
remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE);
remoteViews.setEmptyView(R.id.conversation_list, R.id.empty_conversation_list);
WidgetService.configureValidWidgetIntents(context, remoteViews, appWidgetId, account,
folderType, folderUri, folderConversationListUri, folderDisplayName, widgetService);
}
public static void configureValidWidgetIntents(Context context, RemoteViews remoteViews,
int appWidgetId, Account account, final int folderType, final Uri folderUri,
final Uri folderConversationListUri, final String folderDisplayName,
Class<?> serviceClass) {
remoteViews.setViewVisibility(R.id.widget_configuration, View.GONE);
// Launch an intent to avoid ANRs
final Intent intent = new Intent(context, serviceClass);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(Utils.EXTRA_ACCOUNT, account.serialize());
intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_TYPE, folderType);
intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_URI, folderUri);
intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_CONVERSATION_LIST_URI,
folderConversationListUri);
intent.putExtra(BaseWidgetProvider.EXTRA_FOLDER_DISPLAY_NAME, folderDisplayName);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
remoteViews.setRemoteAdapter(R.id.conversation_list, intent);
// Open mail app when click on header
final Intent mailIntent = Utils.createViewFolderIntent(context, folderUri, account);
PendingIntent clickIntent = PendingIntent.getActivity(context, 0, mailIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.widget_header, clickIntent);
// On click intent for Compose
final Intent composeIntent = new Intent();
composeIntent.setAction(Intent.ACTION_SEND);
composeIntent.putExtra(Utils.EXTRA_ACCOUNT, account.serialize());
composeIntent.setData(account.composeIntentUri);
composeIntent.putExtra(ComposeActivity.EXTRA_FROM_EMAIL_TASK, true);
if (account.composeIntentUri != null) {
composeIntent.putExtra(Utils.EXTRA_COMPOSE_URI, account.composeIntentUri);
}
// Build a task stack that forces the conversation list on the stack before the compose
// activity.
final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
clickIntent = taskStackBuilder.addNextIntent(mailIntent)
.addNextIntent(composeIntent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.widget_compose, clickIntent);
// On click intent for Conversation
final Intent conversationIntent = new Intent();
conversationIntent.setAction(Intent.ACTION_VIEW);
clickIntent = PendingIntent.getActivity(context, 0, conversationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setPendingIntentTemplate(R.id.conversation_list, clickIntent);
}
/**
* Persists the information about the specified widget.
*/
public static void saveWidgetInformation(Context context, int appWidgetId, Account account,
final String folderUri) {
MailPrefs.get(context).configureWidget(appWidgetId, account, folderUri);
}
/**
* Returns true if this widget id has been configured and saved.
*/
public boolean isWidgetConfigured(Context context, int appWidgetId, Account account) {
return isAccountValid(context, account) &&
MailPrefs.get(context).isWidgetConfigured(appWidgetId);
}
protected boolean isAccountValid(Context context, Account account) {
if (account != null) {
Account[] accounts = AccountUtils.getSyncingAccounts(context);
for (Account existing : accounts) {
if (existing != null && account.uri.equals(existing.uri)) {
return true;
}
}
}
return false;
}
/**
* Remote Views Factory for Mail Widget.
*/
protected static class MailFactory
implements RemoteViewsService.RemoteViewsFactory, OnLoadCompleteListener<Cursor> {
private static final int MAX_CONVERSATIONS_COUNT = 25;
private static final int MAX_SENDERS_LENGTH = 25;
private static final int FOLDER_LOADER_ID = 0;
private static final int CONVERSATION_CURSOR_LOADER_ID = 1;
private static final int ACCOUNT_LOADER_ID = 2;
private final Context mContext;
private final int mAppWidgetId;
private final Account mAccount;
private final int mFolderType;
private final Uri mFolderUri;
private final Uri mFolderConversationListUri;
private final String mFolderDisplayName;
private final WidgetConversationListItemViewBuilder mWidgetConversationListItemViewBuilder;
private CursorLoader mConversationCursorLoader;
private Cursor mConversationCursor;
private CursorLoader mFolderLoader;
private CursorLoader mAccountLoader;
private FolderUpdateHandler mFolderUpdateHandler;
private int mFolderCount;
private boolean mShouldShowViewMore;
private boolean mFolderInformationShown = false;
private final WidgetService mService;
private String mSendersSplitToken;
private String mElidedPaddingToken;
private TextAppearanceSpan mUnreadStyle;
private TextAppearanceSpan mReadStyle;
public MailFactory(Context context, Intent intent, WidgetService service) {
mContext = context;
mAppWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
mAccount = Account.newinstance(intent.getStringExtra(Utils.EXTRA_ACCOUNT));
mFolderType = intent.getIntExtra(WidgetProvider.EXTRA_FOLDER_TYPE, FolderType.DEFAULT);
mFolderDisplayName = intent.getStringExtra(WidgetProvider.EXTRA_FOLDER_DISPLAY_NAME);
final Uri folderUri = intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_URI);
final Uri folderConversationListUri =
intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_CONVERSATION_LIST_URI);
if (folderUri != null && folderConversationListUri != null) {
mFolderUri = folderUri;
mFolderConversationListUri = folderConversationListUri;
} else {
// This is a old intent created in version UR8 (or earlier).
String folderString = intent.getStringExtra(Utils.EXTRA_FOLDER);
//noinspection deprecation
Folder folder = Folder.fromString(folderString);
if (folder != null) {
mFolderUri = folder.folderUri.fullUri;
mFolderConversationListUri = folder.conversationListUri;
} else {
mFolderUri = Uri.EMPTY;
mFolderConversationListUri = Uri.EMPTY;
// this will mark the widget as unconfigured
BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
mFolderUri, mFolderConversationListUri, mFolderDisplayName);
}
}
mWidgetConversationListItemViewBuilder = new WidgetConversationListItemViewBuilder(
context);
mService = service;
}
@Override
public void onCreate() {
// Save the map between widgetId and account to preference
saveWidgetInformation(mContext, mAppWidgetId, mAccount, mFolderUri.toString());
// If the account of this widget has been removed, we want to update the widget to
// "Tap to configure" mode.
if (!mService.isWidgetConfigured(mContext, mAppWidgetId, mAccount)) {
BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
mFolderUri, mFolderConversationListUri, mFolderDisplayName);
}
mFolderInformationShown = false;
// We want to limit the query result to 25 and don't want these queries to cause network
// traffic
// We also want this cursor to receive notifications on all changes. Any change that
// the user made locally, the default policy of the UI provider is to not send
// notifications for. But in this case, since the widget is not using the
// ConversationCursor instance that the UI is using, the widget would not be updated.
final Uri.Builder builder = mFolderConversationListUri.buildUpon();
final String maxConversations = Integer.toString(MAX_CONVERSATIONS_COUNT);
final Uri widgetConversationQueryUri = builder
.appendQueryParameter(ConversationListQueryParameters.LIMIT, maxConversations)
.appendQueryParameter(ConversationListQueryParameters.USE_NETWORK,
Boolean.FALSE.toString())
.appendQueryParameter(ConversationListQueryParameters.ALL_NOTIFICATIONS,
Boolean.TRUE.toString()).build();
final Resources res = mContext.getResources();
mConversationCursorLoader = new CursorLoader(mContext, widgetConversationQueryUri,
UIProvider.CONVERSATION_PROJECTION, null, null, null);
mConversationCursorLoader.registerListener(CONVERSATION_CURSOR_LOADER_ID, this);
mConversationCursorLoader.setUpdateThrottle(
res.getInteger(R.integer.widget_refresh_delay_ms));
mConversationCursorLoader.startLoading();
mSendersSplitToken = res.getString(R.string.senders_split_token);
mElidedPaddingToken = res.getString(R.string.elided_padding_token);
mFolderLoader = new CursorLoader(mContext, mFolderUri, UIProvider.FOLDERS_PROJECTION,
null, null, null);
mFolderLoader.registerListener(FOLDER_LOADER_ID, this);
mFolderUpdateHandler = new FolderUpdateHandler(
res.getInteger(R.integer.widget_folder_refresh_delay_ms));
mFolderUpdateHandler.scheduleTask();
mAccountLoader = new CursorLoader(mContext, mAccount.uri,
UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES, null, null, null);
mAccountLoader.registerListener(ACCOUNT_LOADER_ID, this);
mAccountLoader.startLoading();
}
@Override
public void onDestroy() {
synchronized (sWidgetLock) {
if (mConversationCursorLoader != null) {
mConversationCursorLoader.reset();
mConversationCursorLoader.unregisterListener(this);
mConversationCursorLoader = null;
}
// The Loader should close the cursor, so just unset the reference
// to it here.
mConversationCursor = null;
}
if (mFolderLoader != null) {
mFolderLoader.reset();
mFolderLoader.unregisterListener(this);
mFolderLoader = null;
}
if (mAccountLoader != null) {
mAccountLoader.reset();
mAccountLoader.unregisterListener(this);
mAccountLoader = null;
}
}
@Override
public void onDataSetChanged() {
// We are not using this as signal to requery the cursor. The query will be started
// in the following ways:
// 1) The Service is started and the loader is started in onCreate()
// This will happen when the service is not running, and
// AppWidgetManager#notifyAppWidgetViewDataChanged() is called
// 2) The service is running, with a previously created loader. The loader is watching
// for updates from the existing cursor. If one is seen, the loader will load a new
// cursor in the background.
mFolderUpdateHandler.scheduleTask();
}
/**
* Returns the number of items should be shown in the widget list. This method also updates
* the boolean that indicates whether the "show more" item should be shown.
* @return the number of items to be displayed in the list.
*/
@Override
public int getCount() {
synchronized (sWidgetLock) {
final int count = getConversationCount();
final int cursorCount = mConversationCursor != null ?
mConversationCursor.getCount() : 0;
mShouldShowViewMore = count < cursorCount || count < mFolderCount;
return count + (mShouldShowViewMore ? 1 : 0);
}
}
/**
* Returns the number of conversations that should be shown in the widget. This method
* doesn't update the boolean that indicates that the "show more" item should be included
* in the list.
* @return count
*/
private int getConversationCount() {
synchronized (sWidgetLock) {
final int cursorCount = mConversationCursor != null ?
mConversationCursor.getCount() : 0;
return Math.min(cursorCount, MAX_CONVERSATIONS_COUNT);
}
}
/**
* @return the {@link RemoteViews} for a specific position in the list.
*/
@Override
public RemoteViews getViewAt(int position) {
synchronized (sWidgetLock) {
// "View more conversations" view.
if (mConversationCursor == null || mConversationCursor.isClosed()
|| (mShouldShowViewMore && position >= getConversationCount())) {
return getViewMoreConversationsView();
}
if (!mConversationCursor.moveToPosition(position)) {
// If we ever fail to move to a position, return the
// "View More conversations"
// view.
LogUtils.e(LOG_TAG, "Failed to move to position %d in the cursor.", position);
return getViewMoreConversationsView();
}
Conversation conversation = new Conversation(mConversationCursor);
// Split the senders and status from the instructions.
SpannableStringBuilder senderBuilder = new SpannableStringBuilder();
ArrayList<SpannableString> senders = new ArrayList<SpannableString>();
SendersView.format(mContext, conversation.conversationInfo, "",
MAX_SENDERS_LENGTH, senders, null, null, mAccount.name, true);
senderBuilder = ellipsizeStyledSenders(senders);
// Get styled date.
CharSequence date = DateUtils.getRelativeTimeSpanString(mContext,
conversation.dateMs);
final int ignoreFolderType;
if ((mFolderType & FolderType.INBOX) != 0) {
ignoreFolderType = FolderType.INBOX;
} else {
ignoreFolderType = -1;
}
// Load up our remote view.
RemoteViews remoteViews = mWidgetConversationListItemViewBuilder.getStyledView(date,
conversation, new FolderUri(mFolderUri), ignoreFolderType,
senderBuilder, filterTag(conversation.subject));
// On click intent.
remoteViews.setOnClickFillInIntent(R.id.widget_conversation_list_item,
Utils.createViewConversationIntent(mContext, conversation, mFolderUri,
mAccount));
return remoteViews;
}
}
private SpannableStringBuilder ellipsizeStyledSenders(
ArrayList<SpannableString> styledSenders) {
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString prevSender = null;
for (SpannableString sender : styledSenders) {
if (sender == null) {
LogUtils.e(LOG_TAG, "null sender while iterating over styledSenders");
continue;
}
CharacterStyle[] spans = sender.getSpans(0, sender.length(), CharacterStyle.class);
if (SendersView.sElidedString.equals(sender.toString())) {
prevSender = sender;
sender = copyStyles(spans, mElidedPaddingToken + sender + mElidedPaddingToken);
} else if (builder.length() > 0
&& (prevSender == null || !SendersView.sElidedString.equals(prevSender
.toString()))) {
prevSender = sender;
sender = copyStyles(spans, mSendersSplitToken + sender);
} else {
prevSender = sender;
}
builder.append(sender);
}
return builder;
}
private static SpannableString copyStyles(CharacterStyle[] spans, CharSequence newText) {
SpannableString s = new SpannableString(newText);
if (spans != null && spans.length > 0) {
s.setSpan(spans[0], 0, s.length(), 0);
}
return s;
}
/**
* @return the "View more conversations" view.
*/
private RemoteViews getViewMoreConversationsView() {
RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
view.setTextViewText(
R.id.loading_text, mContext.getText(R.string.view_more_conversations));
view.setOnClickFillInIntent(R.id.widget_loading,
Utils.createViewFolderIntent(mContext, mFolderUri, mAccount));
return view;
}
@Override
public RemoteViews getLoadingView() {
RemoteViews view = new RemoteViews(mContext.getPackageName(), R.layout.widget_loading);
view.setTextViewText(
R.id.loading_text, mContext.getText(R.string.loading_conversation));
return view;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
final RemoteViews remoteViews =
new RemoteViews(mContext.getPackageName(), R.layout.widget);
if (!mService.isWidgetConfigured(mContext, mAppWidgetId, mAccount)) {
BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
mFolderUri, mFolderConversationListUri, mFolderDisplayName);
}
if (loader == mFolderLoader) {
if (!isDataValid(data)) {
// Our folder may have disappeared on us
BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
mFolderUri, mFolderConversationListUri, mFolderDisplayName);
return;
}
final int unreadCount = data.getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN);
final String folderName = data.getString(UIProvider.FOLDER_NAME_COLUMN);
mFolderCount = data.getInt(UIProvider.FOLDER_TOTAL_COUNT_COLUMN);
if (!mFolderInformationShown && !TextUtils.isEmpty(folderName) &&
!TextUtils.isEmpty(mAccount.name)) {
// We want to do a full update to the widget at least once, as the widget
// manager doesn't cache the state of the remote views when doing a partial
// widget update. This causes the folder name to be shown as blank if the state
// of the widget is restored.
mService.configureValidAccountWidget(mContext, remoteViews, mAppWidgetId,
mAccount, mFolderType, mFolderUri, mFolderConversationListUri,
folderName);
appWidgetManager.updateAppWidget(mAppWidgetId, remoteViews);
mFolderInformationShown = true;
}
// There is no reason to overwrite a valid non-null folder name with an empty string
if (!TextUtils.isEmpty(folderName)) {
remoteViews.setViewVisibility(R.id.widget_folder, View.VISIBLE);
remoteViews.setTextViewText(R.id.widget_folder, folderName);
} else {
LogUtils.e(LOG_TAG, "Empty folder name");
}
if (!TextUtils.isEmpty(mAccount.name)) {
remoteViews.setTextViewText(R.id.widget_account_noflip, mAccount.name);
remoteViews.setTextViewText(R.id.widget_account, mAccount.name);
}
final CharSequence unreadCountString = Utils
.getUnreadMessageString(mContext.getApplicationContext(), unreadCount);
// If there are 0 unread messages, hide the unread count text view.
// Otherwise, show the unread count.
if (unreadCount == 0) {
remoteViews.setViewVisibility(R.id.widget_account_noflip, View.VISIBLE);
remoteViews.setViewVisibility(R.id.widget_account_unread_flipper, View.GONE);
} else {
remoteViews.setViewVisibility(R.id.widget_account_noflip, View.GONE);
remoteViews.setViewVisibility(R.id.widget_account_unread_flipper, View.VISIBLE);
remoteViews.setTextViewText(R.id.widget_unread_count, unreadCountString);
}
appWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews);
} else if (loader == mConversationCursorLoader) {
// We want to cache the new cursor
synchronized (sWidgetLock) {
if (!isDataValid(data)) {
mConversationCursor = null;
} else {
mConversationCursor = data;
}
}
appWidgetManager.notifyAppWidgetViewDataChanged(mAppWidgetId,
R.id.conversation_list);
if (mConversationCursor == null || mConversationCursor.getCount() == 0) {
remoteViews.setTextViewText(R.id.empty_conversation_list,
mContext.getString(R.string.empty_folder));
appWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews);
}
} else if (loader == mAccountLoader) {
BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
mFolderUri, mFolderConversationListUri, mFolderDisplayName);
}
}
/**
* Returns a boolean indicating whether this cursor has valid data.
* Note: This seeks to the first position in the cursor
*/
private static boolean isDataValid(Cursor cursor) {
return cursor != null && !cursor.isClosed() && cursor.moveToFirst();
}
/**
* If the subject contains the tag of a mailing-list (text surrounded with []), return the
* subject with that tag ellipsized, e.g. "[android-gmail-team] Hello" -> "[andr...] Hello"
*/
private static String filterTag(String subject) {
String result = subject;
if (subject.length() > 0 && subject.charAt(0) == '[') {
int end = subject.indexOf(']');
if (end > 0) {
String tag = subject.substring(1, end);
result = "[" + Utils.ellipsize(tag, 7) + "]" + subject.substring(end + 1);
}
}
return result;
}
/**
* A {@link DelayedTaskHandler} to throttle folder update to a reasonable rate.
*/
private class FolderUpdateHandler extends DelayedTaskHandler {
public FolderUpdateHandler(int refreshDelay) {
super(Looper.myLooper(), refreshDelay);
}
@Override
protected void performTask() {
// Start the loader. The cached data will be returned if present.
if (mFolderLoader != null) {
mFolderLoader.startLoading();
}
}
}
}
}