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 | |
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')
27 files changed, 684 insertions, 120 deletions
diff --git a/java/com/android/contacts/common/compat/DirectoryCompat.java b/java/com/android/contacts/common/compat/DirectoryCompat.java index 85f4a4202..e67087659 100644 --- a/java/com/android/contacts/common/compat/DirectoryCompat.java +++ b/java/com/android/contacts/common/compat/DirectoryCompat.java @@ -48,4 +48,8 @@ public class DirectoryCompat { public static boolean isEnterpriseDirectoryId(long directoryId) { return VERSION.SDK_INT >= VERSION_CODES.N && Directory.isEnterpriseDirectoryId(directoryId); } + + public static boolean isOnlyEnterpriseDirectoryId(long directoryId) { + return isEnterpriseDirectoryId(directoryId) && !isRemoteDirectoryId(directoryId); + } } diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java index 51df70219..baca590b5 100644 --- a/java/com/android/dialer/app/calllog/CallLogAdapter.java +++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java @@ -54,6 +54,7 @@ import android.view.ViewGroup; import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.contacts.common.preference.ContactsPreferences; +import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener; import com.android.dialer.app.calllog.CallLogGroupBuilder.GroupCreator; @@ -381,9 +382,7 @@ public class CallLogAdapter extends GroupingListAdapter if (viewHolder.callType == CallLog.Calls.MISSED_TYPE) { CallLogAsyncTaskUtil.markCallAsRead(activity, viewHolder.callIds); if (activityType == ACTIVITY_TYPE_DIALTACTS) { - if (v.getContext() instanceof CallLogFragmentListener) { - ((CallLogFragmentListener) v.getContext()).updateTabUnreadCounts(); - } else if (v.getContext() instanceof MainActivityPeer.PeerSupplier) { + if (v.getContext() instanceof MainActivityPeer.PeerSupplier) { // This is really bad, but we must do this to prevent a dependency cycle, enforce // best practices in new code, and avoid refactoring DialtactsActivity. ((FragmentUtilListener) @@ -391,8 +390,7 @@ public class CallLogAdapter extends GroupingListAdapter .getImpl(CallLogFragmentListener.class) .updateTabUnreadCounts(); } else { - throw Assert.createIllegalStateFailException( - "View parent does not implement CallLogFragmentListener"); + ((DialtactsActivity) v.getContext()).updateTabUnreadCounts(); } } } diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java index 5949141f1..10e30ff72 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java @@ -31,6 +31,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.notification.missedcalls.MissedCallNotificationCanceller; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; @@ -87,14 +88,6 @@ public class CallLogNotificationsService extends IntentService { context.startService(serviceIntent); } - public static void markSingleNewVoicemailAsOld(Context context, @Nullable Uri voicemailUri) { - LogUtil.enterBlock("CallLogNotificationsService.markSingleNewVoicemailAsOld"); - Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); - serviceIntent.setAction(CallLogNotificationsService.ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD); - serviceIntent.setData(voicemailUri); - context.startService(serviceIntent); - } - public static void cancelAllMissedCalls(Context context) { LogUtil.enterBlock("CallLogNotificationsService.cancelAllMissedCalls"); DialerExecutorComponent.get(context) @@ -175,7 +168,7 @@ public class CallLogNotificationsService extends IntentService { case ACTION_CANCEL_SINGLE_MISSED_CALL: Uri callUri = intent.getData(); CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(this, callUri); - MissedCallNotifier.cancelSingleMissedCallNotification(this, callUri); + MissedCallNotificationCanceller.cancelSingle(this, callUri); TelecomUtil.cancelMissedCallsNotification(this); break; case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION: @@ -196,7 +189,7 @@ public class CallLogNotificationsService extends IntentService { LogUtil.enterBlock("CallLogNotificationsService.cancelAllMissedCallsBackground"); Assert.isWorkerThread(); CallLogNotificationsQueryHelper.markAllMissedCallsInCallLogAsRead(context); - MissedCallNotifier.cancelAllMissedCallNotifications(context); + MissedCallNotificationCanceller.cancelAll(context); TelecomUtil.cancelMissedCallsNotification(context); } diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index 417f8f0f9..14bbdfa56 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -58,7 +58,9 @@ import com.android.dialer.duo.DuoConstants; import com.android.dialer.enrichedcall.FuzzyPhoneNumberMatcher; import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; -import com.android.dialer.notification.NotificationManagerUtils; +import com.android.dialer.notification.missedcalls.MissedCallConstants; +import com.android.dialer.notification.missedcalls.MissedCallNotificationCanceller; +import com.android.dialer.notification.missedcalls.MissedCallNotificationTags; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumberutil.PhoneNumberHelper; import com.android.dialer.precall.PreCall; @@ -71,18 +73,6 @@ import java.util.Set; /** Creates a notification for calls that the user missed (neither answered nor rejected). */ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { - /** Prefix used to generate a unique tag for each missed call notification. */ - private static final String NOTIFICATION_TAG_PREFIX = "MissedCall_"; - /** Common ID for all missed call notifications. */ - private static final int NOTIFICATION_ID = 1; - /** Tag for the group summary notification. */ - private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_MissedCall"; - /** - * Key used to associate all missed call notifications and the summary as belonging to a single - * group. - */ - private static final String GROUP_KEY = "MissedCallGroup"; - private final Context context; private final CallLogNotificationsQueryHelper callLogNotificationsQueryHelper; @@ -126,7 +116,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { if ((newCalls != null && newCalls.isEmpty()) || count == 0) { // No calls to notify about: clear the notification. CallLogNotificationsQueryHelper.markAllMissedCallsInCallLogAsRead(context); - cancelAllMissedCallNotifications(context); + MissedCallNotificationCanceller.cancelAll(context); return; } @@ -226,7 +216,10 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); DialerNotificationManager.notify( - context, GROUP_SUMMARY_NOTIFICATION_TAG, NOTIFICATION_ID, notification); + context, + MissedCallConstants.GROUP_SUMMARY_NOTIFICATION_TAG, + MissedCallConstants.NOTIFICATION_ID, + notification); if (useCallList) { // Do not repost active notifications to prevent erasing post call notes. @@ -240,7 +233,10 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { String callTag = getNotificationTagForCall(call); if (!activeTags.contains(callTag)) { DialerNotificationManager.notify( - context, callTag, NOTIFICATION_ID, getNotificationForCall(call, null)); + context, + callTag, + MissedCallConstants.NOTIFICATION_ID, + getNotificationForCall(call, null)); } } } @@ -286,29 +282,8 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { } } - public static void cancelAllMissedCallNotifications(@NonNull Context context) { - NotificationManagerUtils.cancelAllInGroup(context, GROUP_KEY); - } - - public static void cancelSingleMissedCallNotification( - @NonNull Context context, @Nullable Uri callUri) { - if (callUri == null) { - LogUtil.e( - "MissedCallNotifier.cancelSingleMissedCallNotification", - "unable to cancel notification, uri is null"); - return; - } - // This will also dismiss the group summary if there are no more missed call notifications. - DialerNotificationManager.cancel( - context, getNotificationTagForCallUri(callUri), NOTIFICATION_ID); - } - private static String getNotificationTagForCall(@NonNull NewCall call) { - return getNotificationTagForCallUri(call.callsUri); - } - - private static String getNotificationTagForCallUri(@NonNull Uri callUri) { - return NOTIFICATION_TAG_PREFIX + callUri; + return MissedCallNotificationTags.getNotificationTagForCallUri(call.callsUri); } @WorkerThread @@ -324,7 +299,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { DialerNotificationManager.notify( context, getNotificationTagForCall(call), - NOTIFICATION_ID, + MissedCallConstants.NOTIFICATION_ID, getNotificationForCall(call, note)); return; } @@ -408,7 +383,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { private Notification.Builder createNotificationBuilder() { return new Notification.Builder(context) - .setGroup(GROUP_KEY) + .setGroup(MissedCallConstants.GROUP_KEY) .setSmallIcon(android.R.drawable.stat_notify_missed_call) .setColor(context.getResources().getColor(R.color.dialer_theme_color, null)) .setAutoCancel(true) @@ -437,7 +412,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { public void callBackFromMissedCall(String number, Uri callUri) { closeSystemDialogs(context); CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(context, callUri); - cancelSingleMissedCallNotification(context, callUri); + MissedCallNotificationCanceller.cancelSingle(context, callUri); DialerUtils.startActivityWithErrorToast( context, PreCall.getIntent( @@ -450,7 +425,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { public void sendSmsFromMissedCall(String number, Uri callUri) { closeSystemDialogs(context); CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(context, callUri); - cancelSingleMissedCallNotification(context, callUri); + MissedCallNotificationCanceller.cancelSingle(context, callUri); DialerUtils.startActivityWithErrorToast( context, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } 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()); + } + }; } } diff --git a/java/com/android/dialer/common/concurrent/DefaultFutureCallback.java b/java/com/android/dialer/common/concurrent/DefaultFutureCallback.java new file mode 100644 index 000000000..93ad0faf0 --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DefaultFutureCallback.java @@ -0,0 +1,44 @@ +/* + * 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.common.concurrent; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * Returns a {@link FutureCallback} which does nothing on success and crashes the application on + * failure. + * + * <p>You generally shouldn't use this for futures which should be tied to UI, for those use {@link + * UiListener}. + * + * <p>Can be safely used with {@link MoreExecutors#directExecutor()} + */ +public final class DefaultFutureCallback<T> implements FutureCallback<T> { + + @Override + public void onSuccess(T unused) {} + + @Override + public void onFailure(Throwable throwable) { + ThreadUtil.getUiThreadHandler() + .post( + () -> { + throw new RuntimeException(throwable); + }); + } +} diff --git a/java/com/android/dialer/common/concurrent/UiThreadExecutor.java b/java/com/android/dialer/common/concurrent/UiThreadExecutor.java index ec51ed334..8378d69ce 100644 --- a/java/com/android/dialer/common/concurrent/UiThreadExecutor.java +++ b/java/com/android/dialer/common/concurrent/UiThreadExecutor.java @@ -31,7 +31,7 @@ import javax.inject.Inject; public class UiThreadExecutor extends AbstractListeningExecutorService { @Inject - UiThreadExecutor() {} + public UiThreadExecutor() {} @Override public void shutdown() { diff --git a/java/com/android/dialer/contactactions/IntentModule.java b/java/com/android/dialer/contactactions/IntentModule.java index aa7fd25a6..9a345c669 100644 --- a/java/com/android/dialer/contactactions/IntentModule.java +++ b/java/com/android/dialer/contactactions/IntentModule.java @@ -72,7 +72,7 @@ public class IntentModule implements ContactActionModule { context, new CallIntentBuilder(number, initiationType) .setPhoneAccountHandle(phoneAccountHandle)), - R.string.call, + R.string.voice_call, R.drawable.quantum_ic_call_white_24); } diff --git a/java/com/android/dialer/contactactions/SharedModules.java b/java/com/android/dialer/contactactions/SharedModules.java index 7e72863aa..6d97fcb61 100644 --- a/java/com/android/dialer/contactactions/SharedModules.java +++ b/java/com/android/dialer/contactactions/SharedModules.java @@ -20,14 +20,15 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.provider.ContactsContract; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; +import android.widget.Toast; import com.android.dialer.DialerPhoneNumber; import com.android.dialer.clipboard.ClipboardUtils; import com.android.dialer.util.IntentUtil; import com.android.dialer.util.UriUtils; import java.util.List; +import java.util.Locale; /** * Modules for the bottom sheet that are shared between NewVoicemailFragment and NewCallLogFragment @@ -37,10 +38,15 @@ public class SharedModules { public static void maybeAddModuleForAddingToContacts( Context context, List<ContactActionModule> modules, - @NonNull DialerPhoneNumber number, - @Nullable String name, - @Nullable String lookupUri) { - // TODO(zachh): Only show this for non-spam/blocked numbers. + DialerPhoneNumber number, + String name, + String lookupUri, + boolean isBlocked, + boolean isSpam) { + // Skip showing the menu item for a spam/blocked number. + if (isBlocked || isSpam) { + return; + } // Skip showing the menu item for existing contacts. if (isExistingContact(lookupUri)) { @@ -83,22 +89,148 @@ public class SharedModules { } public static void maybeAddModuleForSendingTextMessage( - Context context, List<ContactActionModule> modules, String originalNumber) { + Context context, + List<ContactActionModule> modules, + String normalizedNumber, + boolean isBlocked) { + // Don't show the option to send a text message if the number is blocked. + if (isBlocked) { + return; + } + // TODO(zachh): There are some conditions where this module should not be shown; consider - // voicemail, business numbers, blocked numbers, spam numbers, etc. - if (!TextUtils.isEmpty(originalNumber)) { + // voicemail, business numbers, etc. + + if (!TextUtils.isEmpty(normalizedNumber)) { modules.add( new IntentModule( context, - IntentUtil.getSendSmsIntent(originalNumber), + IntentUtil.getSendSmsIntent(normalizedNumber), R.string.send_a_message, R.drawable.quantum_ic_message_vd_theme_24)); } } + public static void addModulesHandlingBlockedOrSpamNumber( + Context context, + List<ContactActionModule> modules, + String normalizedNumber, + boolean isBlocked, + boolean isSpam) { + // For a spam number, add two options: + // (1) "Not spam" and "Block", or + // (2) "Not spam" and "Unblock". + if (isSpam) { + addModuleForMarkingNumberAsNonSpam(context, modules, normalizedNumber); + addModuleForBlockingOrUnblockingNumber(context, modules, normalizedNumber, isBlocked); + return; + } + + // For a blocked non-spam number, add "Unblock" option. + if (isBlocked) { + addModuleForBlockingOrUnblockingNumber(context, modules, normalizedNumber, isBlocked); + return; + } + + // For a number that is neither a spam number nor blocked, add "Block/Report spam" option. + addModuleForBlockingNumberAndOptionallyReportingSpam(context, modules, normalizedNumber); + } + + private static void addModuleForMarkingNumberAsNonSpam( + Context context, List<ContactActionModule> modules, String normalizedNumber) { + modules.add( + new ContactActionModule() { + @Override + public int getStringId() { + return R.string.not_spam; + } + + @Override + public int getDrawableId() { + return R.drawable.quantum_ic_report_off_vd_theme_24; + } + + @Override + public boolean onClick() { + // TODO(a bug): implement this method. + Toast.makeText( + context, + String.format(Locale.ENGLISH, "TODO: Report %s as non-spam", normalizedNumber), + Toast.LENGTH_SHORT) + .show(); + return true; // Close the bottom sheet. + } + }); + } + + private static void addModuleForBlockingOrUnblockingNumber( + Context context, + List<ContactActionModule> modules, + String normalizedNumber, + boolean isBlocked) { + modules.add( + new ContactActionModule() { + @Override + public int getStringId() { + return isBlocked ? R.string.unblock_number : R.string.block_number; + } + + @Override + public int getDrawableId() { + return isBlocked + ? R.drawable.ic_unblock // TODO(a bug): use a vector icon + : R.drawable.quantum_ic_block_vd_theme_24; + } + + @Override + public boolean onClick() { + // TODO(a bug): implement this method. + Toast.makeText( + context, + String.format( + Locale.ENGLISH, + "TODO: " + (isBlocked ? "Unblock " : "Block ") + " number %s.", + normalizedNumber), + Toast.LENGTH_SHORT) + .show(); + return true; // Close the bottom sheet. + } + }); + } + + private static void addModuleForBlockingNumberAndOptionallyReportingSpam( + Context context, List<ContactActionModule> modules, String normalizedNumber) { + modules.add( + new ContactActionModule() { + @Override + public int getStringId() { + return R.string.block_and_optionally_report_spam; + } + + @Override + public int getDrawableId() { + return R.drawable.quantum_ic_block_vd_theme_24; + } + + @Override + public boolean onClick() { + // TODO(a bug): implement this method. + Toast.makeText( + context, + String.format( + Locale.ENGLISH, + "TODO: Block and optionally report as spam %s.", + normalizedNumber), + Toast.LENGTH_SHORT) + .show(); + return true; // Close the bottom sheet. + } + }); + } + public static void maybeAddModuleForCopyingNumber( - Context context, List<ContactActionModule> modules, String originalNumber) { - if (TextUtils.isEmpty(originalNumber)) { + Context context, List<ContactActionModule> modules, String normalizedNumber) { + if (TextUtils.isEmpty(normalizedNumber)) { return; } modules.add( @@ -115,7 +247,7 @@ public class SharedModules { @Override public boolean onClick() { - ClipboardUtils.copyText(context, null, originalNumber, true); + ClipboardUtils.copyText(context, null, normalizedNumber, true); return false; } }); diff --git a/java/com/android/dialer/contactactions/res/drawable-xxxhdpi/ic_unblock.png b/java/com/android/dialer/contactactions/res/drawable-xxxhdpi/ic_unblock.png Binary files differnew file mode 100644 index 000000000..01551e2fc --- /dev/null +++ b/java/com/android/dialer/contactactions/res/drawable-xxxhdpi/ic_unblock.png diff --git a/java/com/android/dialer/contactactions/res/values/strings.xml b/java/com/android/dialer/contactactions/res/values/strings.xml index 0e953a56d..4d598a930 100644 --- a/java/com/android/dialer/contactactions/res/values/strings.xml +++ b/java/com/android/dialer/contactactions/res/values/strings.xml @@ -16,13 +16,31 @@ --> <resources> + <!-- Option shown in call log/voicemail menu to make a voice call [CHAR LIMIT=30] --> + <string name="voice_call">Voice call</string> + + <!-- Option shown in a call log/voicemail menu to make a video call [CHAR LIMIT=30] --> + <string name="video_call">Video call</string> + <!-- Option shown in call log menu/voicemail to add the phone number from an entry to an existing contact (also provides option to create a new contact from the number). [CHAR LIMIT=30] --> - <string name="add_to_contacts">Add to contacts</string> + <string name="add_to_contacts">Add contact</string> <!-- Options shown in call log/voicemail menu to send a SMS to the number represented by the call log/voicemailentry. [CHAR LIMIT=30] --> - <string name="send_a_message">Send a message</string> + <string name="send_a_message">Message</string> + + <!-- Options shown in call log/voicemail menu to mark a number as non-spam. [CHAR LIMIT=30] --> + <string name="not_spam">Not spam</string> + + <!-- Options shown in a call log/voicemail menu to block a number. [CHAR LIMIT=30] --> + <string name="block_number">Block</string> + + <!-- Options shown in a call log/voicemail menu to unblock a number. [CHAR LIMIT=30] --> + <string name="unblock_number">Unblock</string> + + <!-- Options shown in a call log/voicemail menu to block a number and/or report it as spam. [CHAR LIMIT=30] --> + <string name="block_and_optionally_report_spam">Block/Report spam</string> <!-- Option displayed in call log/voicemail menu to copy phone number. [CHAR LIMIT=30] --> <string name="copy_number">Copy number</string> diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java index 789648928..0a85667a1 100644 --- a/java/com/android/dialer/main/impl/NewMainActivityPeer.java +++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java @@ -16,16 +16,22 @@ package com.android.dialer.main.impl; +import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.ui.NewCallLogFragment; +import com.android.dialer.common.concurrent.DefaultFutureCallback; import com.android.dialer.main.MainActivityPeer; import com.android.dialer.main.impl.bottomnav.BottomNavBar; import com.android.dialer.main.impl.bottomnav.BottomNavBar.OnBottomNavTabSelectedListener; import com.android.dialer.main.impl.bottomnav.BottomNavBar.TabIndex; import com.android.dialer.voicemail.listui.NewVoicemailFragment; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; /** MainActivityPeer that implements the new fragments. */ public class NewMainActivityPeer implements MainActivityPeer { @@ -40,7 +46,8 @@ public class NewMainActivityPeer implements MainActivityPeer { public void onActivityCreate(Bundle saveInstanceState) { mainActivity.setContentView(R.layout.main_activity); MainBottomNavBarBottomNavTabListener bottomNavBarBottomNavTabListener = - new MainBottomNavBarBottomNavTabListener(mainActivity.getSupportFragmentManager()); + new MainBottomNavBarBottomNavTabListener( + mainActivity.getSupportFragmentManager(), mainActivity.getApplicationContext()); BottomNavBar bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar); bottomNav.addOnTabSelectedListener(bottomNavBarBottomNavTabListener); bottomNav.selectTab(TabIndex.SPEED_DIAL); @@ -77,9 +84,12 @@ public class NewMainActivityPeer implements MainActivityPeer { private static final String VOICEMAIL_TAG = "voicemail"; private final FragmentManager supportFragmentManager; + private final Context appContext; - private MainBottomNavBarBottomNavTabListener(FragmentManager supportFragmentManager) { + private MainBottomNavBarBottomNavTabListener( + FragmentManager supportFragmentManager, Context appContext) { this.supportFragmentManager = supportFragmentManager; + this.appContext = appContext; } @Override @@ -126,8 +136,18 @@ public class NewMainActivityPeer implements MainActivityPeer { private void hideAllFragments() { FragmentTransaction supportTransaction = supportFragmentManager.beginTransaction(); - if (supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) { - supportTransaction.hide(supportFragmentManager.findFragmentByTag(CALL_LOG_TAG)); + Fragment callLogFragment = supportFragmentManager.findFragmentByTag(CALL_LOG_TAG); + if (callLogFragment != null) { + if (callLogFragment.isVisible()) { + // If the user taps any bottom nav button and the call log is showing, immediately cancel + // missed calls (unbold them and clear their notifications). + Futures.addCallback( + // TODO(zachh): Use dagger to create Peer and MainBottomNavBarBottomNavTabListener. + CallLogComponent.get(appContext).getClearMissedCalls().clearAll(), + new DefaultFutureCallback<>(), + MoreExecutors.directExecutor()); + } + supportTransaction.hide(callLogFragment); } if (supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) { supportTransaction.hide(supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG)); diff --git a/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java b/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java index a580aee68..2945e39a9 100644 --- a/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java +++ b/java/com/android/dialer/main/impl/bottomnav/BottomNavBar.java @@ -162,6 +162,7 @@ public final class BottomNavBar extends LinearLayout { } } + @TabIndex public int getSelectedTab() { return selectedTab; } diff --git a/java/com/android/dialer/notification/missedcalls/MissedCallConstants.java b/java/com/android/dialer/notification/missedcalls/MissedCallConstants.java new file mode 100644 index 000000000..8553ea58e --- /dev/null +++ b/java/com/android/dialer/notification/missedcalls/MissedCallConstants.java @@ -0,0 +1,36 @@ +/* + * 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.notification.missedcalls; + +/** Constants related to missed call notifications. */ +public final class MissedCallConstants { + + /** Prefix used to generate a unique tag for each missed call notification. */ + public static final String NOTIFICATION_TAG_PREFIX = "MissedCall_"; + + /** Common ID for all missed call notifications. */ + public static final int NOTIFICATION_ID = 1; + + /** Tag for the group summary notification. */ + public static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_MissedCall"; + + /** + * Key used to associate all missed call notifications and the summary as belonging to a single + * group. + */ + public static final String GROUP_KEY = "MissedCallGroup"; +} diff --git a/java/com/android/dialer/notification/missedcalls/MissedCallNotificationCanceller.java b/java/com/android/dialer/notification/missedcalls/MissedCallNotificationCanceller.java new file mode 100644 index 000000000..8798c2127 --- /dev/null +++ b/java/com/android/dialer/notification/missedcalls/MissedCallNotificationCanceller.java @@ -0,0 +1,48 @@ +/* + * 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.notification.missedcalls; + +import android.content.Context; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.DialerNotificationManager; +import com.android.dialer.notification.NotificationManagerUtils; + +/** Cancels missed calls notifications. */ +public final class MissedCallNotificationCanceller { + + /** Cancels all missed call notifications. */ + public static void cancelAll(@NonNull Context context) { + NotificationManagerUtils.cancelAllInGroup(context, MissedCallConstants.GROUP_KEY); + } + + /** Cancels a missed call notification for a single call. */ + public static void cancelSingle(@NonNull Context context, @Nullable Uri callUri) { + if (callUri == null) { + LogUtil.e( + "MissedCallNotificationCanceller.cancelSingle", + "unable to cancel notification, uri is null"); + return; + } + // This will also dismiss the group summary if there are no more missed call notifications. + DialerNotificationManager.cancel( + context, + MissedCallNotificationTags.getNotificationTagForCallUri(callUri), + MissedCallConstants.NOTIFICATION_ID); + } +} diff --git a/java/com/android/dialer/notification/missedcalls/MissedCallNotificationTags.java b/java/com/android/dialer/notification/missedcalls/MissedCallNotificationTags.java new file mode 100644 index 000000000..64f28eeec --- /dev/null +++ b/java/com/android/dialer/notification/missedcalls/MissedCallNotificationTags.java @@ -0,0 +1,29 @@ +/* + * 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.notification.missedcalls; + +import android.net.Uri; +import android.support.annotation.NonNull; + +/** Static methods related to missed call notification tags. */ +public final class MissedCallNotificationTags { + + /** Gets the notification tag for a single call. */ + public static String getNotificationTagForCallUri(@NonNull Uri callUri) { + return MissedCallConstants.NOTIFICATION_TAG_PREFIX + callUri; + } +} diff --git a/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml index 407207a83..9be7fa046 100644 --- a/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml +++ b/java/com/android/dialer/searchfragment/common/res/layout/search_contact_row.xml @@ -65,4 +65,15 @@ android:tint="@color/dialer_secondary_text_color" android:visibility="gone" android:scaleType="center"/> + + <ImageView + android:id="@+id/work_icon" + android:layout_width="@dimen/search_row_height" + android:layout_height="@dimen/search_row_height" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:padding="@dimen/call_to_action_padding" + android:src="@drawable/ic_work_profile" + android:scaleType="centerInside" + android:visibility="gone"/> </RelativeLayout>
\ No newline at end of file diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java index 9d369003d..4be96fe58 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java @@ -26,8 +26,10 @@ import android.provider.ContactsContract.Contacts; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; +import android.widget.ImageView; import android.widget.QuickContactBadge; import android.widget.TextView; +import com.android.contacts.common.compat.DirectoryCompat; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.contactphoto.ContactPhotoManager; @@ -46,6 +48,7 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder private final TextView nameView; private final TextView numberView; private final QuickContactBadge photo; + private final ImageView workBadge; private String number; @@ -55,6 +58,7 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder photo = view.findViewById(R.id.photo); nameView = view.findViewById(R.id.primary); numberView = view.findViewById(R.id.secondary); + workBadge = view.findViewById(R.id.work_icon); context = view.getContext(); } @@ -74,6 +78,10 @@ public final class RemoteContactViewHolder extends RecyclerView.ViewHolder nameView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name, context)); numberView.setText(QueryBoldingUtil.getNameWithQueryBolded(query, secondaryInfo, context)); + workBadge.setVisibility( + DirectoryCompat.isOnlyEnterpriseDirectoryId(cursor.getDirectoryId()) + ? View.VISIBLE + : View.GONE); if (shouldShowPhoto(cursor)) { nameView.setVisibility(View.VISIBLE); diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java index 9510443b9..653c67041 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java @@ -22,6 +22,7 @@ import android.database.MatrixCursor; import android.database.MergeCursor; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import com.android.contacts.common.compat.DirectoryCompat; import com.android.dialer.common.Assert; import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader; @@ -101,7 +102,12 @@ public final class RemoteContactsCursor extends MergeCursor implements SearchCur private static MatrixCursor createHeaderCursor(Context context, String name, long id) { MatrixCursor headerCursor = new MatrixCursor(PROJECTION, 1); - headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id}); + if (DirectoryCompat.isOnlyEnterpriseDirectoryId(id)) { + headerCursor.addRow( + new Object[] {context.getString(R.string.directory_search_label_work), id}); + } else { + headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id}); + } return headerCursor; } diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java index 9feeb7e99..cf495e49c 100644 --- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java +++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursorLoader.java @@ -27,6 +27,7 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import com.android.contacts.common.compat.DirectoryCompat; import com.android.dialer.searchfragment.common.Projections; import com.android.dialer.searchfragment.directories.DirectoriesCursorLoader.Directory; import java.util.ArrayList; @@ -71,7 +72,14 @@ public final class RemoteContactsCursorLoader extends CursorLoader { Directory directory = directories.get(i); // Filter out local directories - if (!isRemoteDirectory(directory.getId())) { + if (!DirectoryCompat.isRemoteDirectoryId(directory.getId()) + && !DirectoryCompat.isEnterpriseDirectoryId(directory.getId())) { + cursors[i] = null; + continue; + } + + // Filter out invisible directories + if (DirectoryCompat.isInvisibleDirectory(directory.getId())) { cursors[i] = null; continue; } @@ -93,17 +101,6 @@ public final class RemoteContactsCursorLoader extends CursorLoader { return RemoteContactsCursor.newInstance(getContext(), cursors, directories); } - private static boolean isRemoteDirectory(long directoryId) { - return VERSION.SDK_INT >= VERSION_CODES.N - ? ContactsContract.Directory.isRemoteDirectoryId(directoryId) - : (directoryId != ContactsContract.Directory.DEFAULT - && directoryId != ContactsContract.Directory.LOCAL_INVISIBLE - // Directory.ENTERPRISE_DEFAULT is the default work profile directory for locally stored - // contacts - && directoryId != ContactsContract.Directory.ENTERPRISE_DEFAULT - && directoryId != ContactsContract.Directory.ENTERPRISE_LOCAL_INVISIBLE); - } - private MatrixCursor createMatrixCursorFilteringNullNumbers(Cursor cursor) { if (cursor == null) { return null; diff --git a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java index 00899fd69..f28393c0c 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java +++ b/java/com/android/dialer/simulator/impl/SimulatorSimCallManager.java @@ -29,6 +29,7 @@ import android.telecom.TelecomManager; import android.telephony.TelephonyManager; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.strictmode.StrictModeUtils; import java.util.Arrays; import java.util.List; import java.util.Random; @@ -53,17 +54,23 @@ public class SimulatorSimCallManager { static void register(@NonNull Context context) { LogUtil.enterBlock("SimulatorSimCallManager.register"); Assert.isNotNull(context); - TelecomManager telecomManager = context.getSystemService(TelecomManager.class); - telecomManager.registerPhoneAccount(buildSimCallManagerAccount(context)); - telecomManager.registerPhoneAccount(buildVideoProviderAccount(context)); + StrictModeUtils.bypass( + () -> { + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + telecomManager.registerPhoneAccount(buildSimCallManagerAccount(context)); + telecomManager.registerPhoneAccount(buildVideoProviderAccount(context)); + }); } static void unregister(@NonNull Context context) { LogUtil.enterBlock("SimulatorSimCallManager.unregister"); Assert.isNotNull(context); - TelecomManager telecomManager = context.getSystemService(TelecomManager.class); - telecomManager.unregisterPhoneAccount(getSimCallManagerHandle(context)); - telecomManager.unregisterPhoneAccount(getVideoProviderHandle(context)); + StrictModeUtils.bypass( + () -> { + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + telecomManager.unregisterPhoneAccount(getSimCallManagerHandle(context)); + telecomManager.unregisterPhoneAccount(getVideoProviderHandle(context)); + }); } @NonNull diff --git a/java/com/android/dialer/theme/res/values/strings.xml b/java/com/android/dialer/theme/res/values/strings.xml index 74cabadf7..a14693f51 100644 --- a/java/com/android/dialer/theme/res/values/strings.xml +++ b/java/com/android/dialer/theme/res/values/strings.xml @@ -30,9 +30,6 @@ used in the Launcher icon. --> <string name="launcherActivityLabel">Phone</string> - <!-- text on a button, Video call, as in to place a video call. --> - <string name="video_call">Video call</string> - <!-- Label shown on the 'positive' button for the dialog. Indicates that the call will proceed --> <string name="call">Call</string> </resources> diff --git a/java/com/android/dialer/voicemail/listui/menu/Modules.java b/java/com/android/dialer/voicemail/listui/menu/Modules.java index 665031a1f..7254ad651 100644 --- a/java/com/android/dialer/voicemail/listui/menu/Modules.java +++ b/java/com/android/dialer/voicemail/listui/menu/Modules.java @@ -41,16 +41,25 @@ final class Modules { modules, voicemailEntry.number(), voicemailEntry.numberAttributes().getName(), - voicemailEntry.numberAttributes().getLookupUri()); + voicemailEntry.numberAttributes().getLookupUri(), + voicemailEntry.numberAttributes().getIsBlocked(), + voicemailEntry.numberAttributes().getIsSpam()); String normalizedNumber = voicemailEntry.number().getNormalizedNumber(); - SharedModules.maybeAddModuleForSendingTextMessage(context, modules, normalizedNumber); + SharedModules.maybeAddModuleForSendingTextMessage( + context, modules, normalizedNumber, voicemailEntry.numberAttributes().getIsBlocked()); if (!modules.isEmpty()) { modules.add(new DividerModule()); } - // TODO(zachh): Module for blocking/unblocking spam. + SharedModules.addModulesHandlingBlockedOrSpamNumber( + context, + modules, + normalizedNumber, + voicemailEntry.numberAttributes().getIsBlocked(), + voicemailEntry.numberAttributes().getIsSpam()); + // TODO(zachh): Module for CallComposer. SharedModules.maybeAddModuleForCopyingNumber(context, modules, normalizedNumber); |