Add quick contact badge to MessageView.
- Added "quick contact badge" to MessageView - Removed PresenceUpdater, and added new implementation based on Loader, which is much simpler. - Changed some text color, so they're visible now. Bug 2988625 Change-Id: I688a3217178ee8fd0b7245c0ab36a633687ea525
This commit is contained in:
parent
02c0f95734
commit
b1ea9c3c12
|
@ -43,6 +43,13 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="true" >
|
||||
<android.widget.QuickContactBadge
|
||||
android:id="@+id/badge"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="8dip"
|
||||
android:layout_marginLeft="2dip"
|
||||
style="@*android:style/Widget.QuickContactBadge.WindowSmall" />
|
||||
/>
|
||||
<ImageView
|
||||
android:id="@+id/presence"
|
||||
android:src="@drawable/presence_inactive"
|
||||
|
@ -55,7 +62,7 @@
|
|||
android:id="@+id/from"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimaryInverse"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -72,7 +79,7 @@
|
|||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorPrimaryInverse"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="4dip"
|
||||
|
@ -84,7 +91,7 @@
|
|||
android:layout_height="wrap_content" >
|
||||
<TextView
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondaryInverse"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -92,7 +99,7 @@
|
|||
<TextView
|
||||
android:id="@+id/to"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondaryInverse"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -102,7 +109,7 @@
|
|||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorPrimaryInverse"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="4dip"
|
||||
|
@ -114,7 +121,7 @@
|
|||
android:layout_height="wrap_content" >
|
||||
<TextView
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondaryInverse"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -122,7 +129,7 @@
|
|||
<TextView
|
||||
android:id="@+id/cc"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondaryInverse"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -136,7 +143,7 @@
|
|||
<TextView
|
||||
android:id="@+id/subject"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondaryInverse"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="0dip"
|
||||
android:layout_weight="1.0"
|
||||
|
@ -166,7 +173,7 @@
|
|||
android:visibility="gone">
|
||||
<TextView
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondaryInverse"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@string/message_view_show_pictures_instructions"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="0dip"
|
||||
|
|
|
@ -803,12 +803,14 @@ public class Utility {
|
|||
Long defaultValue) {
|
||||
Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||
sortOrder);
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
return c.getLong(column);
|
||||
if (c != null) {
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
return c.getLong(column);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
@ -844,6 +846,23 @@ public class Utility {
|
|||
sortOrder, column, null);
|
||||
}
|
||||
|
||||
public static byte[] getFirstRowBlob(Context context, Uri uri, String[] projection,
|
||||
String selection, String[] selectionArgs, String sortOrder, int column,
|
||||
byte[] defaultValue) {
|
||||
Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||
sortOrder);
|
||||
if (c != null) {
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
return c.getBlob(column);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class used to restore ListView state (e.g. scroll position) when changing adapter.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (C) 2010 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.email.activity;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.Utility;
|
||||
|
||||
import android.content.AsyncTaskLoader;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Photo;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.ContactsContract.Data;
|
||||
import android.provider.ContactsContract.StatusUpdates;
|
||||
|
||||
/**
|
||||
* Loader to load presence statuses and the contact photoes.
|
||||
*/
|
||||
public class ContactStatusLoader extends AsyncTaskLoader<ContactStatusLoader.Result> {
|
||||
public static final int PRESENCE_UNKNOWN_RESOURCE_ID = R.drawable.presence_inactive;
|
||||
|
||||
/** email address -> photo id, presence */
|
||||
/* package */ static final String[] PROJECTION_PHOTO_ID_PRESENCE = new String[] {
|
||||
Contacts.PHOTO_ID,
|
||||
Contacts.CONTACT_PRESENCE
|
||||
};
|
||||
private static final int COLUMN_PHOTO_ID = 0;
|
||||
private static final int COLUMN_PRESENCE = 1;
|
||||
|
||||
/** photo id -> photo data */
|
||||
/* package */ static final String[] PHOTO_PROJECTION = new String[] {
|
||||
Photo.PHOTO
|
||||
};
|
||||
private static final int PHOTO_COLUMN = 0;
|
||||
|
||||
private final Context mContext;
|
||||
private final String mEmailAddress;
|
||||
|
||||
/**
|
||||
* Class that encapsulates the result.
|
||||
*/
|
||||
public static class Result {
|
||||
public static final Result UNKNOWN = new Result(null, PRESENCE_UNKNOWN_RESOURCE_ID, null);
|
||||
|
||||
/** Contact photo. Null if unknown */
|
||||
public final Bitmap mPhoto;
|
||||
|
||||
/** Presence image resource ID. Always has a valid value, even if unknown. */
|
||||
public final int mPresenceResId;
|
||||
|
||||
/** URI for opening quick contact. Null if unknown. */
|
||||
public final Uri mLookupUri;
|
||||
|
||||
public Result(Bitmap photo, int presenceResId, Uri lookupUri) {
|
||||
mPhoto = photo;
|
||||
mPresenceResId = presenceResId;
|
||||
mLookupUri = lookupUri;
|
||||
}
|
||||
}
|
||||
|
||||
public ContactStatusLoader(Context context, String emailAddress) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mEmailAddress = emailAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result loadInBackground() {
|
||||
// Load photo-id and presence status.
|
||||
Uri uri = Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mEmailAddress));
|
||||
Cursor c = mContext.getContentResolver().query(
|
||||
uri,
|
||||
PROJECTION_PHOTO_ID_PRESENCE, null, null, null);
|
||||
if (c == null) {
|
||||
return Result.UNKNOWN;
|
||||
}
|
||||
final long photoId;
|
||||
final int presenceStatus;
|
||||
try {
|
||||
if (!c.moveToFirst()) {
|
||||
return Result.UNKNOWN;
|
||||
}
|
||||
photoId = c.getLong(COLUMN_PHOTO_ID);
|
||||
presenceStatus = c.getInt(COLUMN_PRESENCE);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
// Convert presence status into the res id.
|
||||
final int presenceStatusResId = StatusUpdates.getPresenceIconResourceId(presenceStatus);
|
||||
|
||||
// load photo from photo-id.
|
||||
Bitmap photo = null;
|
||||
if (photoId != -1) {
|
||||
final byte[] photoData = Utility.getFirstRowBlob(mContext,
|
||||
ContentUris.withAppendedId(Data.CONTENT_URI, photoId), PHOTO_PROJECTION,
|
||||
null, null, null, PHOTO_COLUMN, null);
|
||||
if (photoData != null) {
|
||||
photo = BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Get lookup URI
|
||||
final Uri lookupUri = Data.getContactLookupUri(mContext.getContentResolver(), uri);
|
||||
return new Result(photo, presenceStatusResId, lookupUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startLoading() {
|
||||
cancelLoad();
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopLoading() {
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
stopLoading();
|
||||
}
|
||||
}
|
|
@ -35,10 +35,12 @@ import com.android.email.service.AttachmentDownloadService;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.LoaderManager.LoaderCallbacks;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.Loader;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
|
@ -61,6 +63,7 @@ import android.widget.Button;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.QuickContactBadge;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -84,6 +87,7 @@ import java.util.regex.Pattern;
|
|||
* directly. If you need, always load the latest value.
|
||||
*/
|
||||
public abstract class MessageViewFragmentBase extends Fragment implements View.OnClickListener {
|
||||
private static final int PHOTO_LOADER_ID = 1;
|
||||
private Context mContext;
|
||||
|
||||
// Regex that matches start of img tag. '<(?i)img\s+'.
|
||||
|
@ -105,6 +109,7 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
private LinearLayout mAttachments;
|
||||
private ImageView mAttachmentIcon;
|
||||
private View mShowPicturesSection;
|
||||
private QuickContactBadge mFromBadge;
|
||||
private ImageView mSenderPresenceView;
|
||||
private View mScrollView;
|
||||
|
||||
|
@ -115,7 +120,6 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
private LoadMessageTask mLoadMessageTask;
|
||||
private LoadBodyTask mLoadBodyTask;
|
||||
private LoadAttachmentsTask mLoadAttachmentsTask;
|
||||
private PresenceUpdater mPresenceUpdater;
|
||||
|
||||
private java.text.DateFormat mDateFormat;
|
||||
private java.text.DateFormat mTimeFormat;
|
||||
|
@ -134,6 +138,13 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
|
||||
private boolean mIsMessageLoadedForTest;
|
||||
|
||||
private static final int CONTACT_STATUS_STATE_UNLOADED = 0;
|
||||
private static final int CONTACT_STATUS_STATE_UNLOADED_TRIGGERED = 1;
|
||||
private static final int CONTACT_STATUS_STATE_LOADED = 2;
|
||||
|
||||
private int mContactStatusState;
|
||||
private Uri mQuickContactLookupUri;
|
||||
|
||||
/**
|
||||
* Encapsulates known information about a single attachment.
|
||||
*/
|
||||
|
@ -206,7 +217,6 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
mControllerCallback = new ControllerResultUiThreadWrapper<ControllerResults>(
|
||||
new Handler(), new ControllerResults());
|
||||
|
||||
mPresenceUpdater = new PresenceUpdater(mContext);
|
||||
mDateFormat = android.text.format.DateFormat.getDateFormat(mContext); // short format
|
||||
mTimeFormat = android.text.format.DateFormat.getTimeFormat(mContext); // 12/24 date format
|
||||
|
||||
|
@ -232,10 +242,12 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
mAttachments = (LinearLayout) view.findViewById(R.id.attachments);
|
||||
mAttachmentIcon = (ImageView) view.findViewById(R.id.attachment);
|
||||
mShowPicturesSection = view.findViewById(R.id.show_pictures_section);
|
||||
mFromBadge = (QuickContactBadge) view.findViewById(R.id.badge);
|
||||
mSenderPresenceView = (ImageView) view.findViewById(R.id.presence);
|
||||
mScrollView = view.findViewById(R.id.scrollview);
|
||||
|
||||
mFromView.setOnClickListener(this);
|
||||
mFromBadge.setOnClickListener(this);
|
||||
mSenderPresenceView.setOnClickListener(this);
|
||||
view.findViewById(R.id.show_pictures).setOnClickListener(this);
|
||||
|
||||
|
@ -324,9 +336,6 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
mLoadBodyTask = null;
|
||||
Utility.cancelTaskInterrupt(mLoadAttachmentsTask);
|
||||
mLoadAttachmentsTask = null;
|
||||
if (mPresenceUpdater != null) {
|
||||
mPresenceUpdater.cancelAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -358,11 +367,15 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
return mAccountId;
|
||||
}
|
||||
|
||||
protected void openMessageIfStarted() {
|
||||
protected final void openMessageIfStarted() {
|
||||
if (!mStarted) {
|
||||
return;
|
||||
}
|
||||
cancelAllTasks();
|
||||
resetView();
|
||||
}
|
||||
|
||||
protected void resetView() {
|
||||
if (mMessageContentView != null) {
|
||||
mMessageContentView.getSettings().setBlockNetworkLoads(true);
|
||||
mMessageContentView.scrollTo(0, 0);
|
||||
|
@ -374,32 +387,40 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
mAttachmentIcon.setVisibility(View.GONE);
|
||||
mLoadMessageTask = new LoadMessageTask(true);
|
||||
mLoadMessageTask.execute();
|
||||
initContactStatusViews();
|
||||
}
|
||||
|
||||
private void initContactStatusViews() {
|
||||
mContactStatusState = CONTACT_STATUS_STATE_UNLOADED;
|
||||
mQuickContactLookupUri = null;
|
||||
mSenderPresenceView.setImageResource(ContactStatusLoader.PRESENCE_UNKNOWN_RESOURCE_ID);
|
||||
mFromBadge.setImageToDefault();
|
||||
mFromBadge.assignContactFromEmail("", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicks on sender, which shows {@link QuickContact} or prompts to add
|
||||
* the sender as a contact.
|
||||
*
|
||||
* TODO Move DB lookup to a worker thread.
|
||||
*/
|
||||
private void onClickSender() {
|
||||
final Address senderEmail = Address.unpackFirst(mMessage.mFrom);
|
||||
if (senderEmail == null) return;
|
||||
|
||||
// First perform lookup query to find existing contact
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
final String address = senderEmail.getAddress();
|
||||
final Uri dataUri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_FILTER_URI,
|
||||
Uri.encode(address));
|
||||
final Uri lookupUri = ContactsContract.Data.getContactLookupUri(resolver, dataUri);
|
||||
if (mContactStatusState == CONTACT_STATUS_STATE_UNLOADED) {
|
||||
// Status not loaded yet.
|
||||
mContactStatusState = CONTACT_STATUS_STATE_UNLOADED_TRIGGERED;
|
||||
return;
|
||||
}
|
||||
if (mContactStatusState == CONTACT_STATUS_STATE_UNLOADED_TRIGGERED) {
|
||||
return; // Already clicked, and waiting for the data.
|
||||
}
|
||||
|
||||
if (lookupUri != null) {
|
||||
// Found matching contact, trigger QuickContact
|
||||
QuickContact.showQuickContact(mContext, mSenderPresenceView, lookupUri,
|
||||
QuickContact.MODE_LARGE, null);
|
||||
if (mQuickContactLookupUri != null) {
|
||||
QuickContact.showQuickContact(mContext, mFromBadge, mQuickContactLookupUri,
|
||||
QuickContact.MODE_LARGE, null);
|
||||
} else {
|
||||
// No matching contact, ask user to create one
|
||||
final Uri mailUri = Uri.fromParts("mailto", address, null);
|
||||
final Uri mailUri = Uri.fromParts("mailto", senderEmail.getAddress(), null);
|
||||
final Intent intent = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT,
|
||||
mailUri);
|
||||
|
||||
|
@ -418,6 +439,44 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
}
|
||||
}
|
||||
|
||||
private static class ContactStatusLoaderCallbacks
|
||||
implements LoaderCallbacks<ContactStatusLoader.Result> {
|
||||
private static final String BUNDLE_EMAIL_ADDRESS = "email";
|
||||
private final MessageViewFragmentBase mFragment;
|
||||
|
||||
public ContactStatusLoaderCallbacks(MessageViewFragmentBase fragment) {
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
public static Bundle createArguments(String emailAddress) {
|
||||
Bundle b = new Bundle();
|
||||
b.putString(BUNDLE_EMAIL_ADDRESS, emailAddress);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<ContactStatusLoader.Result> onCreateLoader(int id, Bundle args) {
|
||||
return new ContactStatusLoader(mFragment.mContext,
|
||||
args.getString(BUNDLE_EMAIL_ADDRESS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<ContactStatusLoader.Result> loader,
|
||||
ContactStatusLoader.Result result) {
|
||||
boolean triggered =
|
||||
(mFragment.mContactStatusState == CONTACT_STATUS_STATE_UNLOADED_TRIGGERED);
|
||||
mFragment.mContactStatusState = CONTACT_STATUS_STATE_LOADED;
|
||||
mFragment.mQuickContactLookupUri = result.mLookupUri;
|
||||
mFragment.mSenderPresenceView.setImageResource(result.mPresenceResId);
|
||||
if (result.mPhoto != null) { // photo will be null if unknown.
|
||||
mFragment.mFromBadge.setImageBitmap(result.mPhoto);
|
||||
}
|
||||
if (triggered) {
|
||||
mFragment.onClickSender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onSaveAttachment(AttachmentInfo info) {
|
||||
if (!Utility.isExternalStorageMounted()) {
|
||||
/*
|
||||
|
@ -520,6 +579,7 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
}
|
||||
switch (view.getId()) {
|
||||
case R.id.from:
|
||||
case R.id.badge:
|
||||
case R.id.presence:
|
||||
onClickSender();
|
||||
break;
|
||||
|
@ -542,29 +602,21 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
}
|
||||
|
||||
/**
|
||||
* Start checking presence of the sender of the message.
|
||||
*
|
||||
* Note: This is just a polling operation. A more advanced solution would be to keep the
|
||||
* cursor open and respond to presence status updates (in the form of content change
|
||||
* notifications). However, because presence changes fairly slowly compared to the duration
|
||||
* of viewing a single message, a simple poll at message load (and onResume) should be
|
||||
* sufficient.
|
||||
* Start loading contact photo and presence.
|
||||
*/
|
||||
private void startPresenceCheck() {
|
||||
// Set "unknown" presence icon.
|
||||
mSenderPresenceView.setImageResource(PresenceUpdater.getPresenceIconResourceId(null));
|
||||
private void queryContactStatus() {
|
||||
initContactStatusViews(); // Initialize the state, just in case.
|
||||
|
||||
// Find the sender email address, and start presence check.
|
||||
if (mMessage != null) {
|
||||
Address sender = Address.unpackFirst(mMessage.mFrom);
|
||||
if (sender != null) {
|
||||
String email = sender.getAddress();
|
||||
if (email != null) {
|
||||
mPresenceUpdater.checkPresence(email, new PresenceUpdater.Callback() {
|
||||
@Override
|
||||
public void onPresenceResult(String emailAddress, Integer presenceStatus) {
|
||||
mSenderPresenceView.setImageResource(
|
||||
PresenceUpdater.getPresenceIconResourceId(presenceStatus));
|
||||
}
|
||||
});
|
||||
mFromBadge.assignContactFromEmail(email, false);
|
||||
getLoaderManager().restartLoader(PHOTO_LOADER_ID,
|
||||
ContactStatusLoaderCallbacks.createArguments(email),
|
||||
new ContactStatusLoaderCallbacks(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -625,7 +677,7 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
|
|||
mMessageId = message.mId;
|
||||
|
||||
reloadUiFromMessage(message, mOkToFetch);
|
||||
startPresenceCheck();
|
||||
queryContactStatus();
|
||||
mCallback.onMessageViewShown(mMailboxType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2010 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.email.activity;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.Utility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.CommonDataKinds;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.ContactsContract.StatusUpdates;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class to check presence for email addresses and update icons.
|
||||
*
|
||||
* In this class, "presence status" is represented by an {@link Integer}, which can take one of:
|
||||
* {@link StatusUpdates#OFFLINE},
|
||||
* {@link StatusUpdates#INVISIBLE},
|
||||
* {@link StatusUpdates#AWAY},
|
||||
* {@link StatusUpdates#IDLE},
|
||||
* {@link StatusUpdates#DO_NOT_DISTURB},
|
||||
* {@link StatusUpdates#AVAILABLE},
|
||||
* or null for unknown.
|
||||
*/
|
||||
public class PresenceUpdater {
|
||||
/* package */ static final String[] PRESENCE_STATUS_PROJECTION =
|
||||
new String[] { Contacts.CONTACT_PRESENCE };
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
/** List of running {@link PresenceCheckTask}. Used in {@link #cancelAll()}. */
|
||||
private final ArrayList<PresenceCheckTask> mTaskList = new ArrayList<PresenceCheckTask>();
|
||||
|
||||
/** Callback called when {@link #checkPresence} is done. */
|
||||
public interface Callback {
|
||||
public void onPresenceResult(String emailAddress, Integer presenceStatus);
|
||||
}
|
||||
|
||||
public PresenceUpdater(Context context) {
|
||||
mContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a task to check presence. Call {@code Callback#onPresenceResult} when done.
|
||||
*
|
||||
* Must be called on the UI thread, as it creates an AsyncTask.
|
||||
*/
|
||||
public void checkPresence(String emailAddress, Callback callback) {
|
||||
PresenceCheckTask task = new PresenceCheckTask(emailAddress, callback);
|
||||
task.execute();
|
||||
synchronized (mTaskList) {
|
||||
mTaskList.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTaskFromList(PresenceCheckTask task) {
|
||||
synchronized (mTaskList) {
|
||||
mTaskList.remove(task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all running tasks.
|
||||
*/
|
||||
public void cancelAll() {
|
||||
synchronized (mTaskList) {
|
||||
try {
|
||||
for (PresenceCheckTask task : mTaskList) {
|
||||
Utility.cancelTaskInterrupt(task);
|
||||
}
|
||||
} finally {
|
||||
mTaskList.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the resourece ID for the presence icon for {@code presenceStatus}.
|
||||
*/
|
||||
/* package */ static int getPresenceIconResourceId(Integer presenceStatus) {
|
||||
return (presenceStatus == null) ? R.drawable.presence_inactive
|
||||
: StatusUpdates.getPresenceIconResourceId(presenceStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual method to get presence status from the contacts provider.
|
||||
* Called on a worker thread.
|
||||
*
|
||||
* Extracted from {@link PresenceCheckTask} for testing.
|
||||
*
|
||||
* @return presence status
|
||||
*/
|
||||
/* package */ Integer getPresenceStatus(String emailAddress) {
|
||||
Cursor cursor = openPresenceCheckCursor(emailAddress);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null; // Unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Open cursor for presence.
|
||||
*
|
||||
* Unit tests override this to inject a mock cursor.
|
||||
*/
|
||||
/* package */ Cursor openPresenceCheckCursor(String emailAddress) {
|
||||
return mContext.getContentResolver().query(ContactsContract.Data.CONTENT_URI,
|
||||
PRESENCE_STATUS_PROJECTION,
|
||||
CommonDataKinds.Email.DATA + "=?", new String[] { emailAddress }, null);
|
||||
}
|
||||
|
||||
private class PresenceCheckTask extends AsyncTask<Void, Void, Integer> {
|
||||
private final String mEmailAddress;
|
||||
private final Callback mCallback;
|
||||
|
||||
public PresenceCheckTask(String emailAddress, Callback callback) {
|
||||
mEmailAddress = emailAddress;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
return getPresenceStatus(mEmailAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
removeTaskFromList(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Integer status) {
|
||||
try {
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
mCallback.onPresenceResult(mEmailAddress, status);
|
||||
} finally {
|
||||
removeTaskFromList(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ int getTaskListSizeForTest() {
|
||||
return mTaskList.size();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright (C) 2010 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.email.activity;
|
||||
|
||||
import com.android.email.activity.ContactStatusLoader.Result;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.StatusUpdates;
|
||||
import android.test.ProviderTestCase2;
|
||||
import android.test.mock.MockContentProvider;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
/**
|
||||
* Test for {@link ContactStatusLoader}
|
||||
*
|
||||
* Unfortunately this doesn't check {@link ContactStatusLoader.Result#mLookupUri}, because we don't
|
||||
* (shouldn't) know how {@link android.provider.ContactsContract.Data#getContactLookupUri} is
|
||||
* implemented.
|
||||
*/
|
||||
public class ContactStatusLoaderTest
|
||||
extends ProviderTestCase2<ContactStatusLoaderTest.MockContactProvider> {
|
||||
private static final String EMAIL = "a@b.c";
|
||||
|
||||
private MockContactProvider mProvider;
|
||||
|
||||
public ContactStatusLoaderTest() {
|
||||
super(MockContactProvider.class, ContactsContract.AUTHORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mProvider = getProvider();
|
||||
}
|
||||
|
||||
// Contact doesn't exist
|
||||
public void testContactNotFound() {
|
||||
// Insert empty cursor
|
||||
mProvider.mCursors.offer(
|
||||
new MatrixCursor(ContactStatusLoader.PROJECTION_PHOTO_ID_PRESENCE));
|
||||
|
||||
// Load!
|
||||
ContactStatusLoader l = new ContactStatusLoader(getMockContext(), EMAIL);
|
||||
Result r = l.loadInBackground();
|
||||
|
||||
// Check input to the provider
|
||||
assertEquals(1, mProvider.mUris.size());
|
||||
assertEquals("content://com.android.contacts/data/emails/lookup/a%40b.c",
|
||||
mProvider.mUris.get(0));
|
||||
|
||||
// Check result
|
||||
assertNull(r.mPhoto);
|
||||
assertEquals(ContactStatusLoader.PRESENCE_UNKNOWN_RESOURCE_ID, r.mPresenceResId);
|
||||
}
|
||||
|
||||
// Contact doesn't exist -- provider returns null for the first query
|
||||
public void testNull() {
|
||||
// No cursor prepared. (Mock provider will return null)
|
||||
|
||||
// Load!
|
||||
ContactStatusLoader l = new ContactStatusLoader(getMockContext(), EMAIL);
|
||||
Result r = l.loadInBackground();
|
||||
|
||||
// Check result
|
||||
assertNull(r.mPhoto);
|
||||
assertEquals(ContactStatusLoader.PRESENCE_UNKNOWN_RESOURCE_ID, r.mPresenceResId);
|
||||
}
|
||||
|
||||
// Contact exists, but no photo
|
||||
public void testNoPhoto() {
|
||||
// Result for the first query (the one for photo-id)
|
||||
MatrixCursor cursor1 = new MatrixCursor(ContactStatusLoader.PROJECTION_PHOTO_ID_PRESENCE);
|
||||
cursor1.addRow(new Object[]{12345, StatusUpdates.AWAY});
|
||||
mProvider.mCursors.offer(cursor1);
|
||||
|
||||
// Empty cursor for the second query
|
||||
mProvider.mCursors.offer(new MatrixCursor(ContactStatusLoader.PHOTO_PROJECTION));
|
||||
|
||||
// Load!
|
||||
ContactStatusLoader l = new ContactStatusLoader(getMockContext(), EMAIL);
|
||||
Result r = l.loadInBackground();
|
||||
|
||||
// Check input to the provider
|
||||
// We should have had at least two queries from loadInBackground.
|
||||
// There can be extra queries from getContactLookupUri(), but this test shouldn't know
|
||||
// the details, so use ">= 2".
|
||||
assertTrue(mProvider.mUris.size() >= 2);
|
||||
assertEquals("content://com.android.contacts/data/emails/lookup/a%40b.c",
|
||||
mProvider.mUris.get(0));
|
||||
assertEquals("content://com.android.contacts/data/12345",
|
||||
mProvider.mUris.get(1));
|
||||
|
||||
// Check result
|
||||
assertNull(r.mPhoto); // no photo
|
||||
assertEquals(android.R.drawable.presence_away, r.mPresenceResId);
|
||||
}
|
||||
|
||||
// Contact exists, but no photo (provider returns null for the second query)
|
||||
public void testNull2() {
|
||||
// Result for the first query (the one for photo-id)
|
||||
MatrixCursor cursor1 = new MatrixCursor(ContactStatusLoader.PROJECTION_PHOTO_ID_PRESENCE);
|
||||
cursor1.addRow(new Object[]{12345, StatusUpdates.AWAY});
|
||||
mProvider.mCursors.offer(cursor1);
|
||||
|
||||
// No cursor for the second query
|
||||
|
||||
// Load!
|
||||
ContactStatusLoader l = new ContactStatusLoader(getMockContext(), EMAIL);
|
||||
Result r = l.loadInBackground();
|
||||
|
||||
// Check result
|
||||
assertNull(r.mPhoto); // no photo
|
||||
assertEquals(android.R.drawable.presence_away, r.mPresenceResId);
|
||||
}
|
||||
|
||||
// Contact exists, with a photo
|
||||
public void testWithPhoto() {
|
||||
// Result for the first query (the one for photo-id)
|
||||
MatrixCursor cursor1 = new MatrixCursor(ContactStatusLoader.PROJECTION_PHOTO_ID_PRESENCE);
|
||||
cursor1.addRow(new Object[]{12345, StatusUpdates.AWAY});
|
||||
mProvider.mCursors.offer(cursor1);
|
||||
|
||||
// Prepare for the second query.
|
||||
MatrixCursor cursor2 = new PhotoCursor(createJpegData(10, 20));
|
||||
mProvider.mCursors.offer(cursor2);
|
||||
|
||||
// Load!
|
||||
ContactStatusLoader l = new ContactStatusLoader(getMockContext(), EMAIL);
|
||||
Result r = l.loadInBackground();
|
||||
|
||||
// Check result
|
||||
assertNotNull(r.mPhoto);
|
||||
assertEquals(10, r.mPhoto.getWidth());
|
||||
assertEquals(android.R.drawable.presence_away, r.mPresenceResId);
|
||||
}
|
||||
|
||||
private static byte[] createJpegData(int width, int height) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
// MatrixCursor doesn't support getBlob, so use this...
|
||||
private static class PhotoCursor extends MatrixCursor {
|
||||
private final byte[] mBlob;
|
||||
|
||||
public PhotoCursor(byte[] blob) {
|
||||
super(ContactStatusLoader.PHOTO_PROJECTION);
|
||||
mBlob = blob;
|
||||
addRow(new Object[] {null}); // Add dummy row
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBlob(int column) {
|
||||
Assert.assertEquals(0, column);
|
||||
return mBlob;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MockContactProvider extends MockContentProvider {
|
||||
public ArrayList<String> mUris = new ArrayList<String>();
|
||||
|
||||
public final Queue<Cursor> mCursors = new LinkedBlockingQueue<Cursor>();
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
mUris.add(uri.toString());
|
||||
return mCursors.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2010 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.email.activity;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.TestUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.provider.ContactsContract.StatusUpdates;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
/**
|
||||
* Test case for {@link PresenceUpdater}.
|
||||
*
|
||||
* We need to use {@link InstrumentationTestCase} so that we can create AsyncTasks on the UI thread
|
||||
* using {@link InstrumentationTestCase#runTestOnUiThread}.
|
||||
*/
|
||||
@LargeTest
|
||||
public class PresenceUpdaterTest extends InstrumentationTestCase {
|
||||
/**
|
||||
* Email address that's (most probably) not in Contacts.
|
||||
*/
|
||||
private static final String NON_EXISTENT_EMAIL_ADDRESS = "no.such.email.address@a.a";
|
||||
|
||||
/**
|
||||
* Timeout used for async tests.
|
||||
*/
|
||||
private static final int TIMEOUT_SECONDS = 10;
|
||||
|
||||
private Context getContext() {
|
||||
return getInstrumentation().getTargetContext();
|
||||
}
|
||||
|
||||
public void testSetPresenceIcon() {
|
||||
assertEquals(StatusUpdates.getPresenceIconResourceId(StatusUpdates.AWAY),
|
||||
PresenceUpdater.getPresenceIconResourceId(StatusUpdates.AWAY));
|
||||
|
||||
// Special case: unknown
|
||||
assertEquals(R.drawable.presence_inactive, PresenceUpdater.getPresenceIconResourceId(null));
|
||||
}
|
||||
|
||||
/** Call {@link PresenceUpdater#checkPresence} on the UI thread. */
|
||||
private void checkPresenceOnUiThread(final PresenceUpdater pu, final String emailAddress,
|
||||
final PresenceUpdater.Callback callback) throws Throwable {
|
||||
runTestOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pu.checkPresence(emailAddress, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void waitForAllTasksToFinish(String message, final PresenceUpdater pu) {
|
||||
TestUtils.waitUntil(message, new TestUtils.Condition() {
|
||||
@Override public boolean isMet() {
|
||||
return pu.getTaskListSizeForTest() == 0;
|
||||
}
|
||||
}, TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that:
|
||||
* - {@link PresenceUpdater#checkPresence} starts an AsyncTask.
|
||||
* - {@link PresenceUpdater#cancelAll} cancels all AsyncTasks.
|
||||
*
|
||||
* It uses {@link PresenceUpdaterBlocking} to test cancellation.
|
||||
*/
|
||||
public void testQueueTasksAndCancelAll() throws Throwable {
|
||||
// Use blocking one.
|
||||
PresenceUpdaterBlocking pu = new PresenceUpdaterBlocking(getContext());
|
||||
MockCallback callback = new MockCallback();
|
||||
|
||||
// Start presence check.
|
||||
checkPresenceOnUiThread(pu, "dummy@dummy.com", callback);
|
||||
|
||||
// There should be 1 task running.
|
||||
assertEquals(1, pu.getTaskListSizeForTest());
|
||||
|
||||
// Start another presence check.
|
||||
checkPresenceOnUiThread(pu, "dummy2@dummy.com", callback);
|
||||
|
||||
// There should be 2 tasks running.
|
||||
assertEquals(2, pu.getTaskListSizeForTest());
|
||||
|
||||
assertFalse(callback.mCalled);
|
||||
|
||||
// === Test for cancelAll() ===
|
||||
|
||||
// Cancel all tasks. Callback shouldn't get called.
|
||||
callback.reset();
|
||||
pu.cancelAll();
|
||||
|
||||
waitForAllTasksToFinish("testQueueTaskAndCancelAll", pu);
|
||||
|
||||
assertFalse(callback.mCalled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that
|
||||
* - {@link PresenceUpdater#checkPresence} calls {@link PresenceUpdater.Callback} within
|
||||
* timeout.
|
||||
*
|
||||
* It uses the actual contacts provider.
|
||||
*/
|
||||
public void testUpdateImageUnknownEmailAddress() throws Throwable {
|
||||
PresenceUpdater pu = new PresenceUpdater(getContext());
|
||||
MockCallback callback = new MockCallback();
|
||||
|
||||
// Start presence check.
|
||||
checkPresenceOnUiThread(pu, NON_EXISTENT_EMAIL_ADDRESS, callback);
|
||||
|
||||
waitForAllTasksToFinish("testUpdateImageUnknownEmailAddress", pu);
|
||||
|
||||
// Check status
|
||||
assertTrue(callback.mCalled);
|
||||
assertNull(callback.mPresenceStatus);
|
||||
|
||||
// There should be no running tasks.
|
||||
assertEquals(0, pu.getTaskListSizeForTest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that
|
||||
* - startUpdate really updates image's resource ID before timeout for *known* email address.
|
||||
*
|
||||
* It uses {@link PresenceUpdaterWithMockCursor} to inject a mock cursor with a dummy presence
|
||||
* information.
|
||||
*/
|
||||
public void testUpdateImage() throws Throwable {
|
||||
PresenceUpdaterWithMockCursor pu = new PresenceUpdaterWithMockCursor(getContext(),
|
||||
StatusUpdates.AVAILABLE);
|
||||
MockCallback callback = new MockCallback();
|
||||
|
||||
// Start presence check.
|
||||
checkPresenceOnUiThread(pu, NON_EXISTENT_EMAIL_ADDRESS, callback);
|
||||
|
||||
waitForAllTasksToFinish("testUpdateImage", pu);
|
||||
|
||||
// Check status
|
||||
assertTrue(callback.mCalled);
|
||||
assertEquals((Integer) StatusUpdates.AVAILABLE, callback.mPresenceStatus);
|
||||
}
|
||||
|
||||
private static class MockCallback implements PresenceUpdater.Callback {
|
||||
public boolean mCalled;
|
||||
public Integer mPresenceStatus;
|
||||
|
||||
public void reset() {
|
||||
mPresenceStatus = null;
|
||||
mCalled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPresenceResult(String emailAddress, Integer presenceStatus) {
|
||||
mPresenceStatus = presenceStatus;
|
||||
mCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass of {@link PresenceUpdater} whose async task waits for an Object to be notified.
|
||||
*/
|
||||
private static class PresenceUpdaterBlocking extends PresenceUpdater {
|
||||
public final Object mWaitForObject = new Object();
|
||||
|
||||
public PresenceUpdaterBlocking(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override Integer getPresenceStatus(String emailAddress) {
|
||||
synchronized (mWaitForObject) {
|
||||
try {
|
||||
mWaitForObject.wait();
|
||||
} catch (InterruptedException ignore) {
|
||||
// Canceled
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return super.getPresenceStatus(emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A subclass of {@link PresenceUpdater} that injects a MatrixCursor as a mock.
|
||||
*/
|
||||
private static class PresenceUpdaterWithMockCursor extends PresenceUpdater {
|
||||
public final int mPresenceStatus;
|
||||
|
||||
public PresenceUpdaterWithMockCursor(Context context, int presenceStatus) {
|
||||
super(context);
|
||||
mPresenceStatus = presenceStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to inject a mock cursor.
|
||||
*/
|
||||
@Override Cursor openPresenceCheckCursor(String emailAddress) {
|
||||
MatrixCursor c = new MatrixCursor(PresenceUpdater.PRESENCE_STATUS_PROJECTION);
|
||||
c.addRow(new Object[] {mPresenceStatus});
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue