summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/calllog
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2018-02-15 18:08:21 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2018-02-15 18:08:21 +0000
commitc54ce2658988ca36ca3dfab00daefca4dcfed3b2 (patch)
treefb432a423f670969da57a4900b5ff7dcc35d2f8c /java/com/android/dialer/calllog
parent70f98d3997c42c7d4e3b3e3920659aca78820d14 (diff)
parent39009b4ad73d5017295b30fb18a77224195f06af (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')
-rw-r--r--java/com/android/dialer/calllog/CallLogComponent.java2
-rw-r--r--java/com/android/dialer/calllog/ClearMissedCalls.java166
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogFragment.java31
-rw-r--r--java/com/android/dialer/calllog/ui/menu/Modules.java39
-rw-r--r--java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java29
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());
+ }
+ };
}
}