summaryrefslogtreecommitdiff
path: root/src/com/android/dialer/calllog/CallLogAdapter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/dialer/calllog/CallLogAdapter.java')
-rw-r--r--src/com/android/dialer/calllog/CallLogAdapter.java317
1 files changed, 285 insertions, 32 deletions
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 266be34ba..a8cd72a39 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.provider.CallLog.Calls;
@@ -35,20 +36,26 @@ import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.common.widget.GroupingListAdapter;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.util.UriUtils;
+import com.android.dialer.CallDetailActivity;
import com.android.dialer.PhoneCallDetails;
import com.android.dialer.PhoneCallDetailsHelper;
import com.android.dialer.R;
+import com.android.dialer.util.AsyncTaskExecutor;
+import com.android.dialer.util.AsyncTaskExecutors;
import com.android.dialer.util.ExpirableCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
+import java.util.HashMap;
import java.util.LinkedList;
+import java.util.Map;
/**
* Adapter class to fill in data for the Call Log.
@@ -56,6 +63,12 @@ import java.util.LinkedList;
public class CallLogAdapter extends GroupingListAdapter
implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
+
+ /** The enumeration of {@link android.os.AsyncTask} objects used in this class. */
+ public enum Tasks {
+ REMOVE_CALL_LOG_ENTRIES,
+ }
+
/** Interface used to initiate a refresh of the content. */
public interface CallFetcher {
public void fetchCalls();
@@ -103,6 +116,9 @@ public class CallLogAdapter extends GroupingListAdapter
private final CallFetcher mCallFetcher;
private ViewTreeObserver mViewTreeObserver = null;
+ /** Aynchronous task executor, lazy instantiated as needed. */
+ private AsyncTaskExecutor mAsyncTaskExecutor;
+
/**
* A cache of the contact details for the phone numbers in the call log.
* <p>
@@ -113,6 +129,9 @@ public class CallLogAdapter extends GroupingListAdapter
*/
private ExpirableCache<NumberWithCountryIso, ContactInfo> mContactInfoCache;
+ /** Hashmap, keyed by call Id, used to track which call log entries have been expanded or not */
+ private HashMap<Long,Boolean> mIsExpanded = new HashMap<Long,Boolean>();
+
/**
* A request for contact details for the given number.
*/
@@ -186,12 +205,6 @@ public class CallLogAdapter extends GroupingListAdapter
/** Can be set to true by tests to disable processing of requests. */
private volatile boolean mRequestProcessingDisabled = false;
- /**
- * Whether to show the secondary action button used to play voicemail or show call details.
- * True if created from a CallLogFragment.
- * False if created from the PhoneFavoriteFragment. */
- private boolean mShowSecondaryActionButton = true;
-
private boolean mIsCallLog = true;
private int mNumMissedCalls = 0;
private int mNumMissedCallsShown = 0;
@@ -211,6 +224,35 @@ public class CallLogAdapter extends GroupingListAdapter
}
};
+ /**
+ * Click listener for the delete from call log button. Removes the current call log
+ * entry and its associated calls from the call log.
+ */
+ private final View.OnClickListener mDeleteListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Retrieve the views from the call log view.
+ final CallLogListItemViews views =
+ (CallLogListItemViews)
+ ((View)v.getParent().getParent().getParent().getParent()).getTag();
+
+ deleteCalls(views.callIds);
+ notifyDataSetChanged();
+ }
+ };
+
+ /**
+ * The onClickListener used to expand or collapse the action buttons section for a call log
+ * entry.
+ */
+ private final View.OnClickListener mExpandCollapseListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ expandOrCollapseActions((View) v.getParent().getParent());
+ notifyDataSetChanged();
+ }
+ };
+
private void startActivityForAction(View view) {
final IntentProvider intentProvider = (IntentProvider) view.getTag();
if (intentProvider != null) {
@@ -251,14 +293,13 @@ public class CallLogAdapter extends GroupingListAdapter
};
public CallLogAdapter(Context context, CallFetcher callFetcher,
- ContactInfoHelper contactInfoHelper, boolean showSecondaryActionButton,
+ ContactInfoHelper contactInfoHelper,
boolean isCallLog) {
super(context);
mContext = context;
mCallFetcher = callFetcher;
mContactInfoHelper = contactInfoHelper;
- mShowSecondaryActionButton = showSecondaryActionButton;
mIsCallLog = isCallLog;
mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
@@ -512,8 +553,6 @@ public class CallLogAdapter extends GroupingListAdapter
private void findAndCacheViews(View view) {
// Get the views to bind to.
CallLogListItemViews views = CallLogListItemViews.fromView(view);
- views.primaryActionView.setOnClickListener(mActionListener);
- views.secondaryActionButtonView.setOnClickListener(mActionListener);
view.setTag(views);
}
@@ -537,36 +576,55 @@ public class CallLogAdapter extends GroupingListAdapter
final long duration = c.getLong(CallLogQuery.DURATION);
final int callType = c.getInt(CallLogQuery.CALL_TYPE);
final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+ final long rowId = c.getLong(CallLogQuery.ID);
+ views.rowId = rowId;
+
+ // Store some values used when the actions ViewStub is inflated on expansion of the actions
+ // section.
+ views.number = number;
+ views.numberPresentation = numberPresentation;
+ views.callType = callType;
+ views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
+ // Stash away the Ids of the calls so that we can support deleting a row in the call log.
+ views.callIds = getCallIds(c, rowId, count);
final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
final boolean isVoicemailNumber =
PhoneNumberUtilsWrapper.INSTANCE.isVoicemailNumber(number);
- // Primary action is always to call, if possible.
- if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) {
- // Sets the primary action to call the number.
- views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number));
- } else {
- views.primaryActionView.setTag(null);
- }
+ // Where binding and not in the call log, use default behaviour of invoking a call when
+ // tapping the primary view.
+ if (!mIsCallLog) {
+ views.primaryActionView.setOnClickListener(this.mActionListener);
- if ( mShowSecondaryActionButton ) {
- // Store away the voicemail information so we can play it directly.
- if (callType == Calls.VOICEMAIL_TYPE) {
- String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
- final long rowId = c.getLong(CallLogQuery.ID);
- views.secondaryActionButtonView.setTag(
- IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
+ // Set return call intent, otherwise null.
+ if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) {
+ // Sets the primary action to call the number.
+ views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number));
} else {
- // Store the call details information.
- views.secondaryActionButtonView.setTag(
- IntentProvider.getCallDetailIntentProvider(
- getCursor(), c.getPosition(), c.getLong(CallLogQuery.ID), count));
+ // Number is not callable, so hide button.
+ views.primaryActionView.setTag(null);
}
} else {
- // No action enabled.
- views.secondaryActionButtonView.setTag(null);
+ // In the call log, expand/collapse an actions section for the call log entry when
+ // the primary view is tapped.
+
+ // TODO: This needs to be changed to do the proper QP open/close animation.
+ views.primaryActionView.setOnClickListener(this.mExpandCollapseListener);
+
+ // Note: Binding of the action buttons is done as required in configureActionViews
+ // when the user expands the actions ViewStub.
+ }
+
+ // Restore expansion state of the row on rebind. Inflate the actions ViewStub if required,
+ // and set its visibility state accordingly.
+ if (isExpanded(rowId)) {
+ // Inflate the view stub if necessary, and wire up the event handlers.
+ inflateActionViewStub(view);
+ views.actionsView.setVisibility(View.VISIBLE);
+ } else if (views.actionsView != null) {
+ views.actionsView.setVisibility(View.GONE);
}
// Lookup contacts with this number
@@ -631,8 +689,7 @@ public class CallLogAdapter extends GroupingListAdapter
final boolean isNew = c.getInt(CallLogQuery.IS_READ) == 0;
// New items also use the highlighted version of the text.
final boolean isHighlighted = isNew;
- mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted,
- mShowSecondaryActionButton);
+ mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
int contactType = ContactPhotoManager.TYPE_DEFAULT;
@@ -668,6 +725,130 @@ public class CallLogAdapter extends GroupingListAdapter
bindBadge(view, info, details, callType);
}
+ /**
+ * Determines if a call log row with the given Id is expanded to show the action buttons or
+ * not. If the row Id is not yet tracked, add a new entry assuming the row is collapsed.
+ * @param rowId
+ * @return
+ */
+ private boolean isExpanded(long rowId) {
+ if (!mIsExpanded.containsKey(rowId)) {
+ mIsExpanded.put(rowId, false);
+ }
+
+ return mIsExpanded.get(rowId);
+ }
+
+ /**
+ * Toggles the expansion state tracked for the call log row identified by rowId and returns
+ * the new expansion state.
+ *
+ * @param rowId The row Id associated with the call log row to expand/collapse.
+ * @return True where the row is now expanded, false otherwise.
+ */
+ private boolean toggleExpansion(long rowId) {
+ boolean isExpanded = isExpanded(rowId);
+
+ mIsExpanded.put(rowId, !isExpanded);
+ return !isExpanded;
+ }
+
+ /**
+ * Expands or collapses the view containing the CALLBACK, VOICEMAIL and DELETE action buttons.
+ *
+ * @param callLogItem The call log entry parent view.
+ */
+ private void expandOrCollapseActions(View callLogItem) {
+ final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
+
+ // Hide or show the actions view.
+ boolean expanded = toggleExpansion(views.rowId);
+
+ // Inflate the view stub if necessary, and wire up the event handlers.
+ inflateActionViewStub(callLogItem);
+
+ if (expanded) {
+ views.actionsView.setVisibility(View.VISIBLE);
+
+ // Attempt to give accessibility focus to one of the action buttons.
+ // This ensures that a user realizes the expansion occurred.
+ // NOTE(tgunn): requestAccessibilityFocus returns true if the requested
+ // focus was successful. The first successful focus will satisfy the OR
+ // block and block further attempts to set focus.
+ boolean focused = views.callBackButtonView.requestAccessibilityFocus() ||
+ views.voicemailButtonView.requestAccessibilityFocus() ||
+ views.deleteButtonView.requestAccessibilityFocus();
+ } else {
+ views.actionsView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Configures the action buttons in the expandable actions ViewStub. The ViewStub is not
+ * inflated during initial binding, so click handlers, tags and accessibility text must be set
+ * here, if necessary.
+ *
+ * @param callLogItem The call log list item view.
+ */
+ private void inflateActionViewStub(View callLogItem) {
+ final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
+
+ ViewStub stub = (ViewStub)callLogItem.findViewById(R.id.call_log_entry_actions_stub);
+ if (stub != null) {
+ views.actionsView = stub.inflate();
+ }
+
+ if (views.callBackButtonView == null) {
+ views.callBackButtonView = (TextView)views.actionsView.findViewById(R.id.call_back_action);
+ }
+
+ if (views.voicemailButtonView == null) {
+ views.voicemailButtonView = (TextView)views.actionsView.findViewById(R.id.voicemail_action);
+ }
+
+ if ( views.deleteButtonView == null) {
+ views.deleteButtonView = (TextView)views.actionsView.findViewById(R.id.delete_action);
+ }
+
+ bindActionButtons(views);
+ }
+
+ /***
+ * Binds click handlers and intents to the voicemail, delete and callback action buttons.
+ *
+ * @param views The call log item views.
+ */
+ private void bindActionButtons(CallLogListItemViews views) {
+ // Set return call intent, otherwise null.
+ if (PhoneNumberUtilsWrapper.canPlaceCallsTo(views.number, views.numberPresentation)) {
+ // Sets the primary action to call the number.
+ views.callBackButtonView.setTag(
+ IntentProvider.getReturnCallIntentProvider(views.number));
+ views.callBackButtonView.setVisibility(View.VISIBLE);
+ views.callBackButtonView.setOnClickListener(mActionListener);
+ } else {
+ // Number is not callable, so hide button.
+ views.callBackButtonView.setTag(null);
+ views.callBackButtonView.setVisibility(View.GONE);
+ }
+
+ // For voicemail calls, show the "VOICEMAIL" action button; hide otherwise.
+ if (views.callType == Calls.VOICEMAIL_TYPE) {
+ views.voicemailButtonView.setOnClickListener(mActionListener);
+ views.voicemailButtonView.setTag(
+ IntentProvider.getPlayVoicemailIntentProvider(
+ views.rowId, views.voicemailUri));
+ views.voicemailButtonView.setVisibility(View.VISIBLE);
+ } else {
+ views.voicemailButtonView.setTag(null);
+ views.voicemailButtonView.setVisibility(View.GONE);
+ }
+
+ views.deleteButtonView.setOnClickListener(this.mDeleteListener);
+
+ mCallLogViewsHelper.setActionContentDescriptions(views);
+ }
+
protected void bindBadge(View view, ContactInfo info, PhoneCallDetails details, int callType) {
// Do not show badge in call log.
@@ -960,4 +1141,76 @@ public class CallLogAdapter extends GroupingListAdapter
}
return number;
}
+
+ /**
+ * Retrieves the call Ids represented by the current call log row.
+ *
+ * @param cursor Call log cursor to retrieve call Ids from.
+ * @param id Id of the first call of the grouping.
+ * @param groupSize Number of calls associated with the current call log row.
+ * @return Array of call Ids.
+ */
+ private long[] getCallIds(final Cursor cursor, final long id, final int groupSize) {
+ // We want to restore the position in the cursor at the end.
+ int startingPosition = cursor.getPosition();
+ long[] ids = new long[groupSize];
+ // Copy the ids of the rows in the group.
+ for (int index = 0; index < groupSize; ++index) {
+ ids[index] = cursor.getLong(CallLogQuery.ID);
+ cursor.moveToNext();
+ }
+ cursor.moveToPosition(startingPosition);
+ return ids;
+ }
+
+ /**
+ * Retrieves an instance of the asynchronous task executor, creating one if required.
+ * @return The {@link com.android.dialer.util.AsyncTaskExecutor}
+ */
+ private AsyncTaskExecutor getTaskExecutor() {
+ if (mAsyncTaskExecutor == null) {
+ mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
+ }
+ return mAsyncTaskExecutor;
+ }
+
+ /**
+ * Deletes the calls specified in the callIds array, asynchronously.
+ *
+ * @param callIds Ids of calls to be deleted.
+ */
+ private void deleteCalls(long[] callIds) {
+ if (callIds == null) {
+ return;
+ }
+
+ // Build comma separated list of ids to delete.
+ final StringBuilder callIdString = new StringBuilder();
+ for (long callId : callIds) {
+ if (callIdString.length() != 0) {
+ callIdString.append(",");
+ }
+ callIdString.append(callId);
+ }
+
+ // Perform removal of call log entries asynchronously.
+ getTaskExecutor().submit(Tasks.REMOVE_CALL_LOG_ENTRIES,
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ public Void doInBackground(Void... params) {
+ // Issue delete.
+ mContext.getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+ Calls._ID + " IN (" + callIdString + ")", null);
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ // Somewhere went wrong: we're going to bail out and show error to users.
+ Toast.makeText(mContext, R.string.toast_entry_removed,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ );
+ }
}