summaryrefslogtreecommitdiff
path: root/src/com/android
diff options
context:
space:
mode:
authorNancy Chen <nancychen@google.com>2015-08-18 16:39:02 -0700
committerNancy Chen <nancychen@google.com>2015-08-19 20:11:37 -0700
commit2d588b857347fda465a963742c4f61e4014f3441 (patch)
treed6124b35ffab0e725cdba1808e56650d043525f7 /src/com/android
parent4e5df6b3a264cceba8774f6704697312b35f329d (diff)
Show snackbar to undo last deleted voicemail.
Snackbar will appear for 3 seconds during which the user can undo the last deletion. The way it works is the snackbar appears for 3 seconds and a delayed callback is set for 3 seconds after which the voicemail is permanently deleted from the database. If a second (or third or fourth) voicemail is deleted subsequently, the previous voicemails that were waiting for the undo timeout are deleted immediately. Bug: 22460745 Change-Id: I84b70994275975e4e3020321884d382cc87098dc
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/dialer/calllog/CallLogAdapter.java92
-rw-r--r--src/com/android/dialer/calllog/CallLogFragment.java3
-rw-r--r--src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java49
-rw-r--r--src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java18
4 files changed, 151 insertions, 11 deletions
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index a0ce85bd9..477e449b3 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -109,6 +109,9 @@ public class CallLogAdapter extends GroupingListAdapter
// Tracks the rowId of the currently expanded list item, so the position can be updated if there
// are any changes to the call log entries, such as additions or removals.
private long mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM;
+ private int mHiddenPosition = RecyclerView.NO_POSITION;
+ private Uri mHiddenItemUri = null;
+ private boolean mPendingHide = false;
/**
* Hashmap, keyed by call Id, used to track the day group for a call. As call log entries are
@@ -399,7 +402,15 @@ public class CallLogAdapter extends GroupingListAdapter
}
}
- public void pauseCache() {
+ public void onPause() {
+ pauseCache();
+ if (mHiddenItemUri != null) {
+ CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null);
+ }
+ }
+
+ @VisibleForTesting
+ /* package */ void pauseCache() {
mContactInfoCache.stop();
mTelecomCallLogCache.reset();
}
@@ -595,7 +606,8 @@ public class CallLogAdapter extends GroupingListAdapter
@Override
public int getItemCount() {
- return super.getItemCount() + (mShowVoicemailPromoCard ? 1 : 0);
+ return super.getItemCount() + (mShowVoicemailPromoCard ? 1 : 0)
+ - (mHiddenPosition != RecyclerView.NO_POSITION ? 1 : 0);
}
@Override
@@ -615,20 +627,82 @@ public class CallLogAdapter extends GroupingListAdapter
*/
@Override
public Object getItem(int position) {
- return super.getItem(position - (mShowVoicemailPromoCard ? 1 : 0));
+ return super.getItem(position - (mShowVoicemailPromoCard ? 1 : 0)
+ + ((mHiddenPosition != RecyclerView.NO_POSITION && position >= mHiddenPosition)
+ ? 1 : 0));
}
protected boolean isCallLogActivity() {
return mIsCallLogActivity;
}
+ /**
+ * In order to implement the "undo" function, when a voicemail is "deleted" i.e. when the user
+ * clicks the delete button, the deleted item is temporarily hidden from the list. If a user
+ * clicks delete on a second item before the first item's undo option has expired, the first
+ * item is immediately deleted so that only one item can be "undoed" at a time.
+ */
@Override
public void onVoicemailDeleted(Uri uri) {
+ if (mHiddenItemUri == null) {
+ // Immediately hide the currently expanded card.
+ mHiddenPosition = mCurrentlyExpandedPosition;
+ notifyDataSetChanged();
+ } else {
+ // This means that there was a previous item that was hidden in the UI but not
+ // yet deleted from the database (call it a "pending delete"). Delete this previous item
+ // now since it is only possible to do one "undo" at a time.
+ CallLogAsyncTaskUtil.deleteVoicemail(mContext, mHiddenItemUri, null);
+
+ // Set pending hide action so that the current item is hidden only after the previous
+ // item is permanently deleted.
+ mPendingHide = true;
+ }
+
+ collapseExpandedCard();
+
+ // Save the new hidden item uri in case it needs to be deleted from the database when
+ // a user attempts to delete another item.
+ mHiddenItemUri = uri;
+ }
+
+ private void collapseExpandedCard() {
mCurrentlyExpandedRowId = NO_EXPANDED_LIST_ITEM;
mCurrentlyExpandedPosition = RecyclerView.NO_POSITION;
}
/**
+ * When the user clicks "undo", the hidden item is unhidden.
+ */
+ @Override
+ public void onVoicemailDeleteUndo() {
+ mHiddenPosition = RecyclerView.NO_POSITION;
+ mHiddenItemUri = null;
+
+ mPendingHide = false;
+ notifyDataSetChanged();
+ }
+
+ /**
+ * This callback signifies that a database deletion has completed. This means that if there is
+ * an item pending deletion, it will be hidden because the previous item that was in "undo" mode
+ * has been removed from the database. Otherwise it simply resets the hidden state because there
+ * are no pending deletes and thus no hidden items.
+ */
+ @Override
+ public void onVoicemailDeletedInDatabase() {
+ if (mPendingHide) {
+ mHiddenPosition = mCurrentlyExpandedPosition;
+ mPendingHide = false;
+ } else {
+ // There should no longer be any hidden item because it has been deleted from the
+ // database.
+ mHiddenPosition = RecyclerView.NO_POSITION;
+ mHiddenItemUri = null;
+ }
+ }
+
+ /**
* Retrieves the day group of the previous call in the call log. Used to determine if the day
* group has changed and to trigger display of the day group text.
*
@@ -640,8 +714,16 @@ public class CallLogAdapter extends GroupingListAdapter
int startingPosition = cursor.getPosition();
int dayGroup = CallLogGroupBuilder.DAY_GROUP_NONE;
if (cursor.moveToPrevious()) {
- long previousRowId = cursor.getLong(CallLogQuery.ID);
- dayGroup = getDayGroupForCall(previousRowId);
+ // If the previous entry is hidden (deleted in the UI but not in the database), skip it
+ // and check the card above it. A list with the voicemail promo card at the top will be
+ // 1-indexed because the 0th index is the promo card iteself.
+ int previousViewPosition = mShowVoicemailPromoCard ? startingPosition :
+ startingPosition - 1;
+ if (previousViewPosition != mHiddenPosition ||
+ (previousViewPosition == mHiddenPosition && cursor.moveToPrevious())) {
+ long previousRowId = cursor.getLong(CallLogQuery.ID);
+ dayGroup = getDayGroupForCall(previousRowId);
+ }
}
cursor.moveToPosition(startingPosition);
return dayGroup;
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index c63b212d1..dfa895981 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -347,7 +347,7 @@ public class CallLogFragment extends Fragment implements CallLogQueryHandler.Lis
if (mVoicemailPlaybackPresenter != null) {
mVoicemailPlaybackPresenter.onPause();
}
- mAdapter.pauseCache();
+ mAdapter.onPause();
super.onPause();
}
@@ -360,7 +360,6 @@ public class CallLogFragment extends Fragment implements CallLogQueryHandler.Lis
@Override
public void onDestroy() {
- mAdapter.pauseCache();
mAdapter.changeCursor(null);
if (mVoicemailPlaybackPresenter != null) {
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
index 158ed5834..38f6a1773 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
@@ -22,10 +22,12 @@ import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.PowerManager;
import android.provider.VoicemailContract;
import android.util.AttributeSet;
import android.util.Log;
+import android.support.design.widget.Snackbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,6 +38,7 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.android.common.io.MoreCloseables;
+import com.android.dialer.PhoneCallDetails;
import com.android.dialer.R;
import com.android.dialer.calllog.CallLogAsyncTaskUtil;
@@ -58,8 +61,10 @@ import javax.annotation.concurrent.ThreadSafe;
*/
@NotThreadSafe
public class VoicemailPlaybackLayout extends LinearLayout
- implements VoicemailPlaybackPresenter.PlaybackView {
+ implements VoicemailPlaybackPresenter.PlaybackView,
+ CallLogAsyncTaskUtil.CallLogAsyncTaskListener {
private static final String TAG = VoicemailPlaybackLayout.class.getSimpleName();
+ private static final int VOICEMAIL_DELETE_DELAY_MS = 3000;
/**
* Controls the animation of the playback slider.
@@ -184,8 +189,36 @@ public class VoicemailPlaybackLayout extends LinearLayout
return;
}
mPresenter.pausePlayback();
- CallLogAsyncTaskUtil.deleteVoicemail(mContext, mVoicemailUri, null);
mPresenter.onVoicemailDeleted();
+
+ final Uri deleteUri = mVoicemailUri;
+ final Runnable deleteCallback = new Runnable() {
+ @Override
+ public void run() {
+ if (mVoicemailUri == deleteUri) {
+ CallLogAsyncTaskUtil.deleteVoicemail(mContext, deleteUri,
+ VoicemailPlaybackLayout.this);
+ }
+ }
+ };
+
+ final Handler handler = new Handler();
+ // Add a little buffer time in case the user clicked "undo" at the end of the delay
+ // window.
+ handler.postDelayed(deleteCallback, VOICEMAIL_DELETE_DELAY_MS + 50);
+
+ Snackbar.make(VoicemailPlaybackLayout.this, R.string.snackbar_voicemail_deleted,
+ Snackbar.LENGTH_LONG)
+ .setDuration(VOICEMAIL_DELETE_DELAY_MS)
+ .setAction(R.string.snackbar_voicemail_deleted_undo,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mPresenter.onVoicemailDeleteUndo();
+ handler.removeCallbacks(deleteCallback);
+ }
+ })
+ .show();
}
};
@@ -282,7 +315,6 @@ public class VoicemailPlaybackLayout extends LinearLayout
mStateText.setText(getString(R.string.voicemail_playback_error));
}
-
public void onSpeakerphoneOn(boolean on) {
if (mPresenter != null) {
mPresenter.setSpeakerphoneOn(on);
@@ -357,6 +389,17 @@ public class VoicemailPlaybackLayout extends LinearLayout
mPlaybackSeek.setEnabled(true);
}
+ @Override
+ public void onDeleteCall() {}
+
+ @Override
+ public void onDeleteVoicemail() {
+ mPresenter.onVoicemailDeletedInDatabase();
+ }
+
+ @Override
+ public void onGetCallDetails(PhoneCallDetails[] details) {}
+
private String getString(int resId) {
return mContext.getString(resId);
}
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 7270af787..540ffb446 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -94,6 +94,8 @@ public class VoicemailPlaybackPresenter
public interface OnVoicemailDeletedListener {
void onVoicemailDeleted(Uri uri);
+ void onVoicemailDeleteUndo();
+ void onVoicemailDeletedInDatabase();
}
/** The enumeration of {@link AsyncTask} objects we use in this class. */
@@ -730,12 +732,26 @@ public class VoicemailPlaybackPresenter
}
/* package */ void onVoicemailDeleted() {
- // Trampoline the event notification to the interested listener
+ // Trampoline the event notification to the interested listener.
if (mOnVoicemailDeletedListener != null) {
mOnVoicemailDeletedListener.onVoicemailDeleted(mVoicemailUri);
}
}
+ /* package */ void onVoicemailDeleteUndo() {
+ // Trampoline the event notification to the interested listener.
+ if (mOnVoicemailDeletedListener != null) {
+ mOnVoicemailDeletedListener.onVoicemailDeleteUndo();
+ }
+ }
+
+ /* package */ void onVoicemailDeletedInDatabase() {
+ // Trampoline the event notification to the interested listener.
+ if (mOnVoicemailDeletedListener != null) {
+ mOnVoicemailDeletedListener.onVoicemailDeletedInDatabase();
+ }
+ }
+
private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
if (mScheduledExecutorService == null) {
mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);