From d9eb05894f5c9d98e2d67502b01691dc54a1a2e6 Mon Sep 17 00:00:00 2001 From: uabdullah Date: Thu, 14 Dec 2017 09:44:33 -0800 Subject: Support deleting voicemails Allows voicemails to be deleted when tapping on the delete icon. Upon tapping the delete button this first deletes/hides it in the recyler view and then subsequently deletes it from the underlying table. Test: Unit test PiperOrigin-RevId: 179055594 Change-Id: I98bb7694d59b7edfcaa4ae162b194c0ef3a0292e --- .../voicemail/listui/NewVoicemailAdapter.java | 87 +++++++++++++++++++++- .../voicemail/listui/NewVoicemailFragment.java | 1 + .../listui/NewVoicemailMediaPlayerView.java | 35 +++++---- .../voicemail/listui/NewVoicemailViewHolder.java | 6 ++ 4 files changed, 110 insertions(+), 19 deletions(-) (limited to 'java/com/android/dialer/voicemail') diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java index 671a39a67..a4848c8f0 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java @@ -16,23 +16,30 @@ package com.android.dialer.voicemail.listui; import android.app.FragmentManager; +import android.content.Context; import android.database.Cursor; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; import android.support.annotation.IntDef; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllogutils.CallLogDates; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.time.Clock; import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener; @@ -68,6 +75,12 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter /** A valid id for {@link VoicemailEntry} is greater than 0 */ private int currentlyExpandedViewHolderId = -1; + /** + * It takes time to delete voicemails from the server, so we "remove" them and remember the + * positions we removed until a new cursor is ready. + */ + Set deletedVoicemailPosition = new ArraySet<>(); + /** * A set of (re-usable) view holders being used by the recycler view to display voicemails. This * set may include multiple view holder with the same ID and shouldn't be used to lookup a @@ -130,6 +143,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter } public void updateCursor(Cursor updatedCursor) { + deletedVoicemailPosition.clear(); this.cursor = updatedCursor; notifyDataSetChanged(); } @@ -159,6 +173,20 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { LogUtil.enterBlock("NewVoicemailAdapter.onBindViewHolder, pos:" + position); + // Re-request a bind when a viewholder is deleted to ensure correct position + if (deletedVoicemailPosition.contains(position)) { + LogUtil.i( + "NewVoicemailAdapter.onBindViewHolder", + "pos:%d contains deleted voicemail, re-bind. #of deleted voicemail positions: %d", + position, + deletedVoicemailPosition.size()); + // TODO(uabdullah): This should be removed when we support multi-select delete + Assert.checkArgument( + deletedVoicemailPosition.size() == 1, "multi-deletes not currently supported"); + onBindViewHolder(viewHolder, ++position); + return; + } + // TODO(uabdullah): a bug Remove logging, temporarily here for debugging. printHashSet(); // TODO(uabdullah): a bug Remove logging, temporarily here for debugging. @@ -464,6 +492,55 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter onPreparedListener.onPrepared(mediaPlayer.getMediaPlayer()); } + @Override + public void deleteViewHolder( + Context context, + FragmentManager fragmentManager, + NewVoicemailViewHolder expandedViewHolder, + Uri voicemailUri) { + LogUtil.i( + "NewVoicemailAdapter.deleteViewHolder", + "deleting adapter position %d, id:%d, uri:%s ", + expandedViewHolder.getAdapterPosition(), + expandedViewHolder.getViewHolderId(), + String.valueOf(voicemailUri)); + + deletedVoicemailPosition.add(expandedViewHolder.getAdapterPosition()); + + Assert.checkArgument(expandedViewHolder.getViewHolderVoicemailUri().equals(voicemailUri)); + + notifyItemRemoved(expandedViewHolder.getAdapterPosition()); + + Assert.checkArgument(currentlyExpandedViewHolderId == expandedViewHolder.getViewHolderId()); + + collapseExpandedViewHolder(expandedViewHolder); + + Worker, Integer> deleteVoicemail = this::deleteVoicemail; + SuccessListener deleteVoicemailCallBack = this::onVoicemailDeleted; + + DialerExecutorComponent.get(context) + .dialerExecutorFactory() + .createUiTaskBuilder(fragmentManager, "delete_voicemail", deleteVoicemail) + .onSuccess(deleteVoicemailCallBack) + .build() + .executeSerial(new Pair<>(context, voicemailUri)); + } + + private void onVoicemailDeleted(Integer integer) { + LogUtil.i("NewVoicemailAdapter.onVoicemailDeleted", "return value:%d", integer); + Assert.checkArgument(integer == 1, "voicemail delete was not successful"); + } + + @WorkerThread + private Integer deleteVoicemail(Pair contextUriPair) { + Assert.isWorkerThread(); + LogUtil.enterBlock("NewVoicemailAdapter.deleteVoicemail"); + Context context = contextUriPair.first; + Uri uri = contextUriPair.second; + LogUtil.i("NewVoicemailAdapter.deleteVoicemail", "deleting uri:%s", String.valueOf(uri)); + return context.getContentResolver().delete(uri, null, null); + } + /** * This function is called recursively to update the seekbar, duration, play/pause buttons of the * expanded view holder if its playing. @@ -731,6 +808,7 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter @Override public int getItemCount() { + // TODO(uabdullah): a bug Remove logging, temporarily here for debugging. LogUtil.enterBlock("NewVoicemailAdapter.getItemCount"); int numberOfHeaders = 0; if (todayHeaderPosition != Integer.MAX_VALUE) { @@ -739,7 +817,14 @@ final class NewVoicemailAdapter extends RecyclerView.Adapter if (olderHeaderPosition != Integer.MAX_VALUE) { numberOfHeaders++; } - return cursor.getCount() + numberOfHeaders; + // TODO(uabdullah): a bug Remove logging, temporarily here for debugging. + LogUtil.i( + "NewVoicemailAdapter.getItemCount", + "cursor cnt:%d, num of headers:%d, delete size:%d", + cursor.getCount(), + numberOfHeaders, + deletedVoicemailPosition.size()); + return cursor.getCount() + numberOfHeaders - deletedVoicemailPosition.size(); } @RowType diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java index 82e704d39..b4be42455 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java @@ -56,6 +56,7 @@ public final class NewVoicemailFragment extends Fragment implements LoaderCallba LogUtil.i("NewVoicemailFragment.onLoadFinished", "cursor size is %d", data.getCount()); if (recyclerView.getAdapter() == null) { recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + // TODO(uabdullah): Replace getActivity().getFragmentManager() with getChildFragment() recyclerView.setAdapter( new NewVoicemailAdapter( data, System::currentTimeMillis, getActivity().getFragmentManager())); diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java index 3f2de7d00..66a6fea7c 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java @@ -379,8 +379,6 @@ public final class NewVoicemailMediaPlayerView extends LinearLayout { private void sendIntentToDownloadVoicemail(Uri uri) { LogUtil.i("NewVoicemailMediaPlayer.sendIntentToDownloadVoicemail", "uri:%s", uri.toString()); - // Send voicemail fetch request. - Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri); Worker, Pair> getVoicemailSourcePackage = this::queryVoicemailSourcePackage; @@ -399,7 +397,7 @@ public final class NewVoicemailMediaPlayerView extends LinearLayout { Uri uri = booleanUriPair.second; LogUtil.i( "NewVoicemailMediaPlayer.sendIntent", - "srcPkg:%s, uri:%%s", + "srcPkg:%s, uri:%s", sourcePackage, String.valueOf(uri)); Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri); @@ -465,23 +463,11 @@ public final class NewVoicemailMediaPlayerView extends LinearLayout { "NewVoicemailMediaPlayer.phoneButtonListener", "speaker request for voicemailUri: %s", voicemailUri.toString()); - } - }; - - private final View.OnClickListener deleteButtonListener = - new View.OnClickListener() { - @Override - public void onClick(View view) { - LogUtil.i( - "NewVoicemailMediaPlayer.deleteButtonListener", - "delete voicemailUri %s", - voicemailUri.toString()); - // TODO(uabdullah): This will be removed in cl/177404259. It only sets the has_content to - // 0, to allow the annotated call log to change, and refresh the fragment. This is used to - // demo and test the downloading of voicemails from the server. ContentValues contentValues = new ContentValues(); contentValues.put("has_content", 0); - + // TODO(uabdullah): It only sets the has_content to 0, to allow the annotated call log to + // change, and refresh the fragment. This is used to demo and test the downloading of + // voicemails from the server. This will be removed once we implement this listener. try { getContext().getContentResolver().update(voicemailUri, contentValues, "type = 4", null); } catch (Exception e) { @@ -494,6 +480,19 @@ public final class NewVoicemailMediaPlayerView extends LinearLayout { } }; + private final View.OnClickListener deleteButtonListener = + new View.OnClickListener() { + @Override + public void onClick(View view) { + LogUtil.i( + "NewVoicemailMediaPlayer.deleteButtonListener", + "delete voicemailUri %s", + String.valueOf(voicemailUri)); + newVoicemailViewHolderListener.deleteViewHolder( + getContext(), fragmentManager, newVoicemailViewHolder, voicemailUri); + } + }; + /** * This is only called to update the media player view of the seekbar, and the duration and the * play button. For constant updates the adapter should seek track. This is the state when a diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java index 072546552..bc23288bb 100644 --- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java +++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java @@ -363,6 +363,12 @@ final class NewVoicemailViewHolder extends RecyclerView.ViewHolder implements On void pauseViewHolder(NewVoicemailViewHolder expandedViewHolder); void resumePausedViewHolder(NewVoicemailViewHolder expandedViewHolder); + + void deleteViewHolder( + Context context, + FragmentManager fragmentManager, + NewVoicemailViewHolder expandedViewHolder, + Uri uri); } @Override -- cgit v1.2.3