diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-02-15 18:08:21 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-02-15 18:08:21 +0000 |
commit | c54ce2658988ca36ca3dfab00daefca4dcfed3b2 (patch) | |
tree | fb432a423f670969da57a4900b5ff7dcc35d2f8c /java/com/android/dialer/calllog | |
parent | 70f98d3997c42c7d4e3b3e3920659aca78820d14 (diff) | |
parent | 39009b4ad73d5017295b30fb18a77224195f06af (diff) |
Merge changes Ib360d3bc,Iae40d0ab,I486f7b1a,I709a1e30
* changes:
Mark calls as read in new call log.
Restored work profile contacts to Dialer search.
Clicking on a missed call in the call log no longer crashes the app.
Add bottom sheet options for blocked and/or spam numbers in the new call log.
Diffstat (limited to 'java/com/android/dialer/calllog')
5 files changed, 249 insertions, 18 deletions
diff --git a/java/com/android/dialer/calllog/CallLogComponent.java b/java/com/android/dialer/calllog/CallLogComponent.java index c7db2a1b8..bb5bfee2a 100644 --- a/java/com/android/dialer/calllog/CallLogComponent.java +++ b/java/com/android/dialer/calllog/CallLogComponent.java @@ -27,6 +27,8 @@ public abstract class CallLogComponent { public abstract RefreshAnnotatedCallLogWorker getRefreshAnnotatedCallLogWorker(); + public abstract ClearMissedCalls getClearMissedCalls(); + public static CallLogComponent get(Context context) { return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) .callLogComponent(); diff --git a/java/com/android/dialer/calllog/ClearMissedCalls.java b/java/com/android/dialer/calllog/ClearMissedCalls.java new file mode 100644 index 000000000..d216e7b88 --- /dev/null +++ b/java/com/android/dialer/calllog/ClearMissedCalls.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2018 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.dialer.calllog; + +import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.provider.CallLog.Calls; +import android.support.v4.os.UserManagerCompat; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.common.concurrent.Annotations.Ui; +import com.android.dialer.common.database.Selection; +import com.android.dialer.inject.ApplicationContext; +import com.android.dialer.notification.missedcalls.MissedCallNotificationCanceller; +import com.android.dialer.util.PermissionsUtil; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.Collection; +import javax.inject.Inject; + +/** + * Clears missed calls. This includes cancelling notifications and updating the "NEW" status in the + * system call log. + */ +public final class ClearMissedCalls { + + private final Context appContext; + private final ListeningExecutorService backgroundExecutor; + private final ListeningExecutorService uiThreadExecutor; + + @Inject + ClearMissedCalls( + @ApplicationContext Context appContext, + @BackgroundExecutor ListeningExecutorService backgroundExecutor, + @Ui ListeningExecutorService uiThreadExecutor) { + this.appContext = appContext; + this.backgroundExecutor = backgroundExecutor; + this.uiThreadExecutor = uiThreadExecutor; + } + + /** + * Cancels all missed call notifications and marks all "new" missed calls in the system call log + * as "not new". + */ + public ListenableFuture<Void> clearAll() { + ListenableFuture<Void> markNewFuture = markNotNew(ImmutableSet.of()); + ListenableFuture<Void> cancelNotificationsFuture = + uiThreadExecutor.submit( + () -> { + MissedCallNotificationCanceller.cancelAll(appContext); + return null; + }); + + // Note on this usage of whenAllComplete: + // -The returned future completes when all sub-futures complete (whether they fail or not) + // -The returned future fails if any sub-future fails + return Futures.whenAllComplete(markNewFuture, cancelNotificationsFuture) + .call( + () -> { + // Calling get() is necessary to propagate failures. + markNewFuture.get(); + cancelNotificationsFuture.get(); + return null; + }, + MoreExecutors.directExecutor()); + } + + /** + * For the provided set of IDs from the system call log, cancels their missed call notifications + * and marks them "not new". + * + * @param ids IDs from the system call log (see {@link Calls#_ID}}. + */ + public ListenableFuture<Void> clearBySystemCallLogId(Collection<Long> ids) { + ListenableFuture<Void> markNewFuture = markNotNew(ids); + ListenableFuture<Void> cancelNotificationsFuture = + uiThreadExecutor.submit( + () -> { + for (long id : ids) { + Uri callUri = Calls.CONTENT_URI.buildUpon().appendPath(Long.toString(id)).build(); + MissedCallNotificationCanceller.cancelSingle(appContext, callUri); + } + return null; + }); + + // Note on this usage of whenAllComplete: + // -The returned future completes when all sub-futures complete (whether they fail or not) + // -The returned future fails if any sub-future fails + return Futures.whenAllComplete(markNewFuture, cancelNotificationsFuture) + .call( + () -> { + // Calling get() is necessary to propagate failures. + markNewFuture.get(); + cancelNotificationsFuture.get(); + return null; + }, + MoreExecutors.directExecutor()); + } + + /** + * Marks all provided system call log IDs as not new, or if the provided collection is empty, + * marks all calls as not new. + */ + @SuppressLint("MissingPermission") + private ListenableFuture<Void> markNotNew(Collection<Long> ids) { + return backgroundExecutor.submit( + () -> { + if (!UserManagerCompat.isUserUnlocked(appContext)) { + LogUtil.e("ClearMissedCalls.markNotNew", "locked"); + return null; + } + if (!PermissionsUtil.hasCallLogWritePermissions(appContext)) { + LogUtil.e("ClearMissedCalls.markNotNew", "no permission"); + return null; + } + + ContentValues values = new ContentValues(); + values.put(Calls.NEW, 0); + + Selection.Builder selectionBuilder = + Selection.builder() + .and(Selection.column(Calls.NEW).is("=", 1)) + .and(Selection.column(Calls.TYPE).is("=", Calls.MISSED_TYPE)); + if (!ids.isEmpty()) { + selectionBuilder.and(Selection.column(Calls._ID).in(toStrings(ids))); + } + Selection selection = selectionBuilder.build(); + appContext + .getContentResolver() + .update( + Calls.CONTENT_URI, + values, + selection.getSelection(), + selection.getSelectionArgs()); + return null; + }); + } + + private static String[] toStrings(Collection<Long> longs) { + String[] strings = new String[longs.size()]; + int i = 0; + for (long value : longs) { + strings[i++] = Long.toString(value); + } + return strings; + } +} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 10f75ef07..5e676f072 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -18,6 +18,7 @@ package com.android.dialer.calllog.ui; import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; @@ -31,10 +32,14 @@ import com.android.dialer.calllog.CallLogFramework; import com.android.dialer.calllog.CallLogFramework.CallLogUi; import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.common.concurrent.DialerExecutorComponent; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.common.concurrent.UiListener; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.TimeUnit; /** The "new" call log fragment implementation, which is built on top of the annotated call log. */ public final class NewCallLogFragment extends Fragment @@ -46,13 +51,19 @@ public final class NewCallLogFragment extends Fragment * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 * call log entries. */ - private static final long WAIT_MILLIS = 100L; + private static final long REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS = 100L; + + @VisibleForTesting + static final long MARK_ALL_CALLS_READ_WAIT_MILLIS = TimeUnit.SECONDS.toMillis(3); private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; private UiListener<Void> refreshAnnotatedCallLogListener; private RecyclerView recyclerView; @Nullable private Runnable refreshAnnotatedCallLogRunnable; + private boolean shouldMarkCallsRead = false; + private final Runnable setShouldMarkCallsReadTrue = () -> shouldMarkCallsRead = true; + public NewCallLogFragment() { LogUtil.enterBlock("NewCallLogFragment.NewCallLogFragment"); } @@ -103,6 +114,13 @@ public final class NewCallLogFragment extends Fragment ((NewCallLogAdapter) recyclerView.getAdapter()).clearCache(); recyclerView.getAdapter().notifyDataSetChanged(); } + + // We shouldn't mark the calls as read immediately when the 3 second timer expires because we + // don't want to disrupt the UI; instead we set a bit indicating to mark them read when the user + // leaves the fragment (in onPause). + shouldMarkCallsRead = false; + ThreadUtil.getUiThreadHandler() + .postDelayed(setShouldMarkCallsReadTrue, MARK_ALL_CALLS_READ_WAIT_MILLIS); } @Override @@ -113,9 +131,17 @@ public final class NewCallLogFragment extends Fragment // This is pending work that we don't actually need to follow through with. ThreadUtil.getUiThreadHandler().removeCallbacks(refreshAnnotatedCallLogRunnable); + ThreadUtil.getUiThreadHandler().removeCallbacks(setShouldMarkCallsReadTrue); CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); callLogFramework.detachUi(); + + if (shouldMarkCallsRead) { + Futures.addCallback( + CallLogComponent.get(getContext()).getClearMissedCalls().clearAll(), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); + } } @Override @@ -159,7 +185,8 @@ public final class NewCallLogFragment extends Fragment throw new RuntimeException(throwable); }); }; - ThreadUtil.getUiThreadHandler().postDelayed(refreshAnnotatedCallLogRunnable, WAIT_MILLIS); + ThreadUtil.getUiThreadHandler() + .postDelayed(refreshAnnotatedCallLogRunnable, REFRESH_ANNOTATED_CALL_LOG_WAIT_MILLIS); } @Override diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java index d59155810..c85a9fddd 100644 --- a/java/com/android/dialer/calllog/ui/menu/Modules.java +++ b/java/com/android/dialer/calllog/ui/menu/Modules.java @@ -52,15 +52,8 @@ final class Modules { if (canPlaceCalls) { addModuleForVideoOrAudioCall(context, modules, row, normalizedNumber); - - SharedModules.maybeAddModuleForAddingToContacts( - context, - modules, - row.number(), - row.numberAttributes().getName(), - row.numberAttributes().getLookupUri()); - - SharedModules.maybeAddModuleForSendingTextMessage(context, modules, normalizedNumber); + SharedModules.maybeAddModuleForSendingTextMessage( + context, modules, normalizedNumber, row.numberAttributes().getIsBlocked()); } if (!modules.isEmpty()) { @@ -68,10 +61,23 @@ final class Modules { } - // TODO(zachh): Module for blocking/unblocking spam. // TODO(zachh): Module for CallComposer. if (canPlaceCalls) { + SharedModules.maybeAddModuleForAddingToContacts( + context, + modules, + row.number(), + row.numberAttributes().getName(), + row.numberAttributes().getLookupUri(), + row.numberAttributes().getIsBlocked(), + row.numberAttributes().getIsSpam()); + SharedModules.addModulesHandlingBlockedOrSpamNumber( + context, + modules, + normalizedNumber, + row.numberAttributes().getIsBlocked(), + row.numberAttributes().getIsSpam()); SharedModules.maybeAddModuleForCopyingNumber(context, modules, normalizedNumber); } @@ -89,10 +95,23 @@ final class Modules { List<ContactActionModule> modules, CoalescedRow row, String normalizedNumber) { + // If a number is blocked, skip this menu item. + if (row.numberAttributes().getIsBlocked()) { + return; + } + PhoneAccountHandle phoneAccountHandle = TelecomUtil.composePhoneAccountHandle( row.phoneAccountComponentName(), row.phoneAccountId()); + // For a spam number, only audio calls are allowed. + if (row.numberAttributes().getIsSpam()) { + modules.add( + IntentModule.newCallModule( + context, normalizedNumber, phoneAccountHandle, CallInitiationType.Type.CALL_LOG)); + return; + } + if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) { // Add an audio call item for video calls. Clicking the top entry on the bottom sheet will // trigger a video call. diff --git a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java index 81c05135f..02724e628 100644 --- a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java +++ b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java @@ -17,10 +17,15 @@ package com.android.dialer.calllog.ui.menu; import android.content.Context; +import android.provider.CallLog.Calls; import android.view.View; +import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.model.CoalescedRow; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.contactactions.ContactActionBottomSheet; import com.android.dialer.glidephotomanager.GlidePhotoManager; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; /** Handles configuration of the bottom sheet menus for call log entries. */ public final class NewCallLogMenu { @@ -28,11 +33,23 @@ public final class NewCallLogMenu { /** Creates and returns the OnClickListener which opens the menu for the provided row. */ public static View.OnClickListener createOnClickListener( Context context, CoalescedRow row, GlidePhotoManager glidePhotoManager) { - return (view) -> - ContactActionBottomSheet.show( - context, - PrimaryAction.fromRow(context, row), - Modules.fromRow(context, row), - glidePhotoManager); + return view -> { + ContactActionBottomSheet.show( + context, + PrimaryAction.fromRow(context, row), + Modules.fromRow(context, row), + glidePhotoManager); + + // If the user opens the bottom sheet for a new call, clear the notifications and make the row + // not bold immediately. To do this, mark all of the calls in group as not new. + if (row.isNew() && row.callType() == Calls.MISSED_TYPE) { + Futures.addCallback( + CallLogComponent.get(context) + .getClearMissedCalls() + .clearBySystemCallLogId(row.coalescedIds().getCoalescedIdList()), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); + } + }; } } |