summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/app/calllog
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/app/calllog')
-rw-r--r--java/com/android/dialer/app/calllog/CallLogActivity.java55
-rw-r--r--java/com/android/dialer/app/calllog/CallLogAdapter.java447
-rw-r--r--java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java10
-rw-r--r--java/com/android/dialer/app/calllog/CallLogFragment.java155
-rw-r--r--java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java303
-rw-r--r--java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java92
-rw-r--r--java/com/android/dialer/app/calllog/CallLogNotificationsService.java164
-rw-r--r--java/com/android/dialer/app/calllog/CallLogReceiver.java4
-rw-r--r--java/com/android/dialer/app/calllog/ClearCallLogDialog.java128
-rw-r--r--java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java446
-rw-r--r--java/com/android/dialer/app/calllog/DialerQuickContactBadge.java63
-rw-r--r--java/com/android/dialer/app/calllog/IntentProvider.java7
-rw-r--r--java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java156
-rw-r--r--java/com/android/dialer/app/calllog/MissedCallNotifier.java115
-rw-r--r--java/com/android/dialer/app/calllog/PhoneAccountHandles.java62
-rw-r--r--java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java34
-rw-r--r--java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java10
-rw-r--r--java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java59
-rw-r--r--java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java271
-rw-r--r--java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java168
-rw-r--r--java/com/android/dialer/app/calllog/VoicemailQueryHandler.java44
-rw-r--r--java/com/android/dialer/app/calllog/calllogcache/CallLogCache.java75
-rw-r--r--java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipop.java74
-rw-r--r--java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java116
24 files changed, 1921 insertions, 1137 deletions
diff --git a/java/com/android/dialer/app/calllog/CallLogActivity.java b/java/com/android/dialer/app/calllog/CallLogActivity.java
index 443171d3f..1bb894c59 100644
--- a/java/com/android/dialer/app/calllog/CallLogActivity.java
+++ b/java/com/android/dialer/app/calllog/CallLogActivity.java
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
+import android.support.design.widget.Snackbar;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
@@ -31,9 +32,14 @@ import android.view.ViewGroup;
import com.android.contacts.common.list.ViewPagerTabs;
import com.android.dialer.app.DialtactsActivity;
import com.android.dialer.app.R;
+import com.android.dialer.calldetails.CallDetailsActivity;
+import com.android.dialer.constants.ActivityRequestCodes;
import com.android.dialer.database.CallLogQueryHandler;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
+import com.android.dialer.logging.UiAction;
+import com.android.dialer.performancereport.PerformanceReport;
+import com.android.dialer.postcall.PostCall;
import com.android.dialer.util.TransactionSafeActivity;
import com.android.dialer.util.ViewUtil;
@@ -48,7 +54,6 @@ public class CallLogActivity extends TransactionSafeActivity
private ViewPagerTabs mViewPagerTabs;
private ViewPagerAdapter mViewPagerAdapter;
private CallLogFragment mAllCallsFragment;
- private CallLogFragment mMissedCallsFragment;
private String[] mTabTitles;
private boolean mIsResumed;
@@ -93,9 +98,16 @@ public class CallLogActivity extends TransactionSafeActivity
@Override
protected void onResume() {
+ // Some calls may not be recorded (eg. from quick contact),
+ // so we should restart recording after these calls. (Recorded call is stopped)
+ PostCall.restartPerformanceRecordingIfARecentCallExist(this);
+ if (!PerformanceReport.isRecording()) {
+ PerformanceReport.startRecording();
+ }
+
mIsResumed = true;
super.onResume();
- sendScreenViewForChildFragment(mViewPager.getCurrentItem());
+ sendScreenViewForChildFragment();
}
@Override
@@ -129,6 +141,7 @@ public class CallLogActivity extends TransactionSafeActivity
}
if (item.getItemId() == android.R.id.home) {
+ PerformanceReport.recordClick(UiAction.Type.CLOSE_CALL_HISTORY_WITH_CANCEL_BUTTON);
final Intent intent = new Intent(this, DialtactsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
@@ -148,7 +161,7 @@ public class CallLogActivity extends TransactionSafeActivity
@Override
public void onPageSelected(int position) {
if (mIsResumed) {
- sendScreenViewForChildFragment(position);
+ sendScreenViewForChildFragment();
}
mViewPagerTabs.onPageSelected(position);
}
@@ -158,7 +171,7 @@ public class CallLogActivity extends TransactionSafeActivity
mViewPagerTabs.onPageScrollStateChanged(state);
}
- private void sendScreenViewForChildFragment(int position) {
+ private void sendScreenViewForChildFragment() {
Logger.get(this).logScreenView(ScreenEvent.Type.CALL_LOG_FILTER, this);
}
@@ -169,6 +182,12 @@ public class CallLogActivity extends TransactionSafeActivity
return position;
}
+ @Override
+ public void onBackPressed() {
+ PerformanceReport.recordClick(UiAction.Type.PRESS_ANDROID_BACK_BUTTON);
+ super.onBackPressed();
+ }
+
/** Adapter for the view pager. */
public class ViewPagerAdapter extends FragmentPagerAdapter {
@@ -189,20 +208,16 @@ public class CallLogActivity extends TransactionSafeActivity
CallLogQueryHandler.CALL_TYPE_ALL, true /* isCallLogActivity */);
case TAB_INDEX_MISSED:
return new CallLogFragment(Calls.MISSED_TYPE, true /* isCallLogActivity */);
+ default:
+ throw new IllegalStateException("No fragment at position " + position);
}
- throw new IllegalStateException("No fragment at position " + position);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
final CallLogFragment fragment = (CallLogFragment) super.instantiateItem(container, position);
- switch (position) {
- case TAB_INDEX_ALL:
+ if (position == TAB_INDEX_ALL) {
mAllCallsFragment = fragment;
- break;
- case TAB_INDEX_MISSED:
- mMissedCallsFragment = fragment;
- break;
}
return fragment;
}
@@ -217,4 +232,22 @@ public class CallLogActivity extends TransactionSafeActivity
return TAB_INDEX_COUNT;
}
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) {
+ if (resultCode == RESULT_OK
+ && data != null
+ && data.getBooleanExtra(CallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) {
+ String number = data.getStringExtra(CallDetailsActivity.EXTRA_PHONE_NUMBER);
+ Snackbar.make(findViewById(R.id.calllog_frame), getString(R.string.ec_data_deleted), 5_000)
+ .setAction(
+ R.string.view_conversation,
+ v -> startActivity(IntentProvider.getSendSmsIntentProvider(number).getIntent(this)))
+ .setActionTextColor(getResources().getColor(R.color.dialer_snackbar_action_text_color))
+ .show();
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
}
diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java
index 2f8a58c8a..61129a7ce 100644
--- a/java/com/android/dialer/app/calllog/CallLogAdapter.java
+++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java
@@ -19,6 +19,7 @@ package com.android.dialer.app.calllog;
import android.app.Activity;
import android.content.ContentUris;
import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
@@ -52,7 +53,6 @@ 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.Bindings;
import com.android.dialer.app.DialtactsActivity;
import com.android.dialer.app.R;
import com.android.dialer.app.calllog.CallLogGroupBuilder.GroupCreator;
@@ -63,31 +63,32 @@ import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter.OnVoicemailDe
import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
import com.android.dialer.calldetails.CallDetailsEntries;
import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
+import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.calllogutils.PhoneAccountUtils;
import com.android.dialer.calllogutils.PhoneCallDetails;
import com.android.dialer.common.Assert;
-import com.android.dialer.common.ConfigProviderBindings;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.AsyncTaskExecutor;
import com.android.dialer.common.concurrent.AsyncTaskExecutors;
+import com.android.dialer.configprovider.ConfigProviderBindings;
import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
import com.android.dialer.enrichedcall.EnrichedCallComponent;
import com.android.dialer.enrichedcall.EnrichedCallManager;
-import com.android.dialer.enrichedcall.historyquery.proto.HistoryResult;
import com.android.dialer.lightbringer.Lightbringer;
import com.android.dialer.lightbringer.LightbringerComponent;
import com.android.dialer.lightbringer.LightbringerListener;
import com.android.dialer.logging.ContactSource;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.UiAction;
+import com.android.dialer.performancereport.PerformanceReport;
import com.android.dialer.phonenumbercache.CallLogQuery;
import com.android.dialer.phonenumbercache.ContactInfo;
import com.android.dialer.phonenumbercache.ContactInfoHelper;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
import com.android.dialer.spam.Spam;
import com.android.dialer.util.PermissionsUtil;
-import java.util.Collections;
-import java.util.List;
+import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
@@ -105,11 +106,12 @@ public class CallLogAdapter extends GroupingListAdapter
private static final String KEY_EXPANDED_POSITION = "expanded_position";
private static final String KEY_EXPANDED_ROW_ID = "expanded_row_id";
+ private static final String KEY_ACTION_MODE = "action_mode_selected_items";
public static final String LOAD_DATA_TASK_IDENTIFIER = "load_data";
public static final String ENABLE_CALL_LOG_MULTI_SELECT = "enable_call_log_multiselect";
- public static final boolean ENABLE_CALL_LOG_MULTI_SELECT_FLAG = false;
+ public static final boolean ENABLE_CALL_LOG_MULTI_SELECT_FLAG = true;
protected final Activity mActivity;
protected final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
@@ -117,6 +119,8 @@ public class CallLogAdapter extends GroupingListAdapter
protected final CallLogCache mCallLogCache;
private final CallFetcher mCallFetcher;
+ private final OnActionModeStateChangedListener mActionModeStateChangedListener;
+ private final MultiSelectRemoveView mMultiSelectRemoveView;
@NonNull private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
private final int mActivityType;
@@ -136,6 +140,8 @@ public class CallLogAdapter extends GroupingListAdapter
private final CallLogAlertManager mCallLogAlertManager;
public ActionMode mActionMode = null;
+ public boolean selectAllMode = false;
+ public boolean deselectAllMode = false;
private final SparseArray<String> selectedItems = new SparseArray<>();
private final ActionMode.Callback mActionModeCallback =
@@ -144,10 +150,17 @@ public class CallLogAdapter extends GroupingListAdapter
// Called when the action mode is created; startActionMode() was called
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ if (mActivity != null) {
+ announceforAccessibility(
+ mActivity.getCurrentFocus(),
+ mActivity.getString(R.string.description_entering_bulk_action_mode));
+ }
mActionMode = mode;
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.actionbar_delete, menu);
+ mMultiSelectRemoveView.showMultiSelectRemoveView(true);
+ mActionModeStateChangedListener.onActionModeStateChanged(true);
return true;
}
@@ -162,10 +175,10 @@ public class CallLogAdapter extends GroupingListAdapter
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (item.getItemId() == R.id.action_bar_delete_menu_item) {
+ Logger.get(mActivity).logImpression(DialerImpression.Type.MULTISELECT_TAP_DELETE_ICON);
if (selectedItems.size() > 0) {
showDeleteSelectedItemsDialog();
}
- mode.finish();
return true;
} else {
return false;
@@ -175,53 +188,78 @@ public class CallLogAdapter extends GroupingListAdapter
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
+ if (mActivity != null) {
+ announceforAccessibility(
+ mActivity.getCurrentFocus(),
+ mActivity.getString(R.string.description_leaving_bulk_action_mode));
+ }
selectedItems.clear();
mActionMode = null;
+ selectAllMode = false;
+ deselectAllMode = false;
+ mMultiSelectRemoveView.showMultiSelectRemoveView(false);
+ mActionModeStateChangedListener.onActionModeStateChanged(false);
notifyDataSetChanged();
}
};
- // Todo (uabdullah): Use plurals http://b/37751831
private void showDeleteSelectedItemsDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
- Assert.checkArgument(selectedItems.size() > 0);
- String voicemailString =
- selectedItems.size() == 1
- ? mActivity.getResources().getString(R.string.voicemailMultiSelectVoicemail)
- : mActivity.getResources().getString(R.string.voicemailMultiSelectVoicemails);
- String deleteVoicemailTitle =
- mActivity
- .getResources()
- .getString(R.string.voicemailMultiSelectDialogTitle, voicemailString);
SparseArray<String> voicemailsToDeleteOnConfirmation = selectedItems.clone();
- builder.setTitle(deleteVoicemailTitle);
-
- builder.setPositiveButton(
- mActivity.getResources().getString(R.string.voicemailMultiSelectDeleteConfirm),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- deleteSelectedItems(voicemailsToDeleteOnConfirmation);
- dialog.cancel();
- }
- });
-
- builder.setNegativeButton(
- mActivity.getResources().getString(R.string.voicemailMultiSelectDeleteCancel),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
-
- AlertDialog dialog = builder.create();
- dialog.show();
+ new AlertDialog.Builder(mActivity, R.style.AlertDialogCustom)
+ .setCancelable(true)
+ .setTitle(
+ mActivity
+ .getResources()
+ .getQuantityString(
+ R.plurals.delete_voicemails_confirmation_dialog_title, selectedItems.size()))
+ .setPositiveButton(
+ R.string.voicemailMultiSelectDeleteConfirm,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int button) {
+ LogUtil.i(
+ "CallLogAdapter.showDeleteSelectedItemsDialog",
+ "onClick, these items to delete " + voicemailsToDeleteOnConfirmation);
+ deleteSelectedItems(voicemailsToDeleteOnConfirmation);
+ mActionMode.finish();
+ dialog.cancel();
+ Logger.get(mActivity)
+ .logImpression(
+ DialerImpression.Type.MULTISELECT_DELETE_ENTRY_VIA_CONFIRMATION_DIALOG);
+ }
+ })
+ .setOnCancelListener(
+ new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialogInterface) {
+ Logger.get(mActivity)
+ .logImpression(
+ DialerImpression.Type
+ .MULTISELECT_CANCEL_CONFIRMATION_DIALOG_VIA_CANCEL_TOUCH);
+ dialogInterface.cancel();
+ }
+ })
+ .setNegativeButton(
+ R.string.voicemailMultiSelectDeleteCancel,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int button) {
+ Logger.get(mActivity)
+ .logImpression(
+ DialerImpression.Type
+ .MULTISELECT_CANCEL_CONFIRMATION_DIALOG_VIA_CANCEL_BUTTON);
+ dialog.cancel();
+ }
+ })
+ .show();
+ Logger.get(mActivity)
+ .logImpression(DialerImpression.Type.MULTISELECT_DISPLAY_DELETE_CONFIRMATION_DIALOG);
}
private void deleteSelectedItems(SparseArray<String> voicemailsToDelete) {
for (int i = 0; i < voicemailsToDelete.size(); i++) {
String voicemailUri = voicemailsToDelete.get(voicemailsToDelete.keyAt(i));
+ LogUtil.i("CallLogAdapter.deleteSelectedItems", "deleting uri:" + voicemailUri);
CallLogAsyncTaskUtil.deleteVoicemail(mActivity, Uri.parse(voicemailUri), null);
}
}
@@ -235,8 +273,13 @@ public class CallLogAdapter extends GroupingListAdapter
&& mVoicemailPlaybackPresenter != null) {
if (v.getId() == R.id.primary_action_view || v.getId() == R.id.quick_contact_photo) {
if (mActionMode == null) {
+ Logger.get(mActivity)
+ .logImpression(
+ DialerImpression.Type.MULTISELECT_LONG_PRESS_ENTER_MULTI_SELECT_MODE);
mActionMode = v.startActionMode(mActionModeCallback);
}
+ Logger.get(mActivity)
+ .logImpression(DialerImpression.Type.MULTISELECT_LONG_PRESS_TAP_ENTRY);
CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) v.getTag();
viewHolder.quickContactView.setVisibility(View.GONE);
viewHolder.checkBoxView.setVisibility(View.VISIBLE);
@@ -248,32 +291,45 @@ public class CallLogAdapter extends GroupingListAdapter
}
};
+ @VisibleForTesting
+ public View.OnClickListener getExpandCollapseListener() {
+ return mExpandCollapseListener;
+ }
+
/** The OnClickListener used to expand or collapse the action buttons of a call log entry. */
private final View.OnClickListener mExpandCollapseListener =
new View.OnClickListener() {
@Override
public void onClick(View v) {
+ PerformanceReport.recordClick(UiAction.Type.CLICK_CALL_LOG_ITEM);
+
CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) v.getTag();
if (viewHolder == null) {
return;
}
if (mActionMode != null && viewHolder.voicemailUri != null) {
+ selectAllMode = false;
+ deselectAllMode = false;
+ mMultiSelectRemoveView.setSelectAllModeToFalse();
int id = getVoicemailId(viewHolder.voicemailUri);
if (selectedItems.get(id) != null) {
- selectedItems.delete(id);
- viewHolder.checkBoxView.setVisibility(View.GONE);
- viewHolder.quickContactView.setVisibility(View.VISIBLE);
+ Logger.get(mActivity)
+ .logImpression(DialerImpression.Type.MULTISELECT_SINGLE_PRESS_UNSELECT_ENTRY);
+ uncheckMarkCallLogEntry(viewHolder, id);
} else {
- viewHolder.quickContactView.setVisibility(View.GONE);
- viewHolder.checkBoxView.setVisibility(View.VISIBLE);
- selectedItems.put(getVoicemailId(viewHolder.voicemailUri), viewHolder.voicemailUri);
- }
-
- if (selectedItems.size() == 0) {
- mActionMode.finish();
- return;
+ Logger.get(mActivity)
+ .logImpression(DialerImpression.Type.MULTISELECT_SINGLE_PRESS_SELECT_ENTRY);
+ checkMarkCallLogEntry(viewHolder);
+ // select all check box logic
+ if (getItemCount() == selectedItems.size()) {
+ LogUtil.i(
+ "mExpandCollapseListener.onClick",
+ "getitem count %d is equal to items select count %d, check select all box",
+ getItemCount(),
+ selectedItems.size());
+ mMultiSelectRemoveView.tapSelectAll();
+ }
}
- mActionMode.setTitle(Integer.toString(selectedItems.size()));
return;
}
@@ -285,14 +341,29 @@ public class CallLogAdapter extends GroupingListAdapter
// If enriched call capabilities were unknown on the initial load,
// viewHolder.isCallComposerCapable may be unset. Check here if we have the capabilities
// as a last attempt at getting them before showing the expanded view to the user
- EnrichedCallCapabilities capabilities =
- getEnrichedCallManager().getCapabilities(viewHolder.number);
- viewHolder.isCallComposerCapable =
- capabilities != null && capabilities.supportsCallComposer();
- generateAndMapNewCallDetailsEntriesHistoryResults(
- viewHolder.number,
- viewHolder.getDetailedPhoneDetails(),
- getAllHistoricalData(viewHolder.number, viewHolder.getDetailedPhoneDetails()));
+ EnrichedCallCapabilities capabilities = null;
+
+ if (viewHolder.number != null) {
+ capabilities = getEnrichedCallManager().getCapabilities(viewHolder.number);
+ }
+
+ if (capabilities == null) {
+ capabilities = EnrichedCallCapabilities.NO_CAPABILITIES;
+ }
+
+ viewHolder.isCallComposerCapable = capabilities.isCallComposerCapable();
+
+ if (capabilities.isTemporarilyUnavailable()) {
+ LogUtil.i(
+ "mExpandCollapseListener.onClick",
+ "%s is temporarily unavailable, requesting capabilities",
+ LogUtil.sanitizePhoneNumber(viewHolder.number));
+ // Refresh the capabilities when temporarily unavailable, see go/ec-temp-unavailable.
+ // Similarly to when we request capabilities the first time, the 'Share and call' button
+ // won't pop in with the new capabilities. Instead the row needs to be collapsed and
+ // expanded again.
+ getEnrichedCallManager().requestCapabilities(viewHolder.number);
+ }
if (viewHolder.rowId == mCurrentlyExpandedRowId) {
// Hide actions, if the clicked item is the expanded item.
@@ -308,10 +379,77 @@ public class CallLogAdapter extends GroupingListAdapter
}
}
expandViewHolderActions(viewHolder);
+
+ if (isLightbringerCallButtonVisible(viewHolder.videoCallButtonView)) {
+ CallIntentBuilder.increaseLightbringerCallButtonAppearInExpandedCallLogItemCount();
+ }
+ }
+ }
+
+ private boolean isLightbringerCallButtonVisible(View videoCallButtonView) {
+ if (videoCallButtonView == null) {
+ return false;
+ }
+ if (videoCallButtonView.getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ IntentProvider intentProvider = (IntentProvider) videoCallButtonView.getTag();
+ if (intentProvider == null) {
+ return false;
+ }
+ String packageName =
+ LightbringerComponent.get(mActivity).getLightbringer().getPackageName();
+ if (packageName == null) {
+ return false;
}
+ return packageName.equals(intentProvider.getIntent(mActivity).getPackage());
}
};
+ private void checkMarkCallLogEntry(CallLogListItemViewHolder viewHolder) {
+ announceforAccessibility(
+ mActivity.getCurrentFocus(),
+ mActivity.getString(
+ R.string.description_selecting_bulk_action_mode, viewHolder.nameOrNumber));
+ viewHolder.quickContactView.setVisibility(View.GONE);
+ viewHolder.checkBoxView.setVisibility(View.VISIBLE);
+ selectedItems.put(getVoicemailId(viewHolder.voicemailUri), viewHolder.voicemailUri);
+ updateActionBar();
+ }
+
+ private void announceforAccessibility(View view, String announcement) {
+ if (view != null) {
+ view.announceForAccessibility(announcement);
+ }
+ }
+
+ private void updateActionBar() {
+ if (mActionMode == null && selectedItems.size() > 0) {
+ Logger.get(mActivity)
+ .logImpression(DialerImpression.Type.MULTISELECT_ROTATE_AND_SHOW_ACTION_MODE);
+ mActivity.startActionMode(mActionModeCallback);
+ }
+ if (mActionMode != null) {
+ mActionMode.setTitle(
+ mActivity
+ .getResources()
+ .getString(
+ R.string.voicemailMultiSelectActionBarTitle,
+ Integer.toString(selectedItems.size())));
+ }
+ }
+
+ private void uncheckMarkCallLogEntry(CallLogListItemViewHolder viewHolder, int id) {
+ announceforAccessibility(
+ mActivity.getCurrentFocus(),
+ mActivity.getString(
+ R.string.description_unselecting_bulk_action_mode, viewHolder.nameOrNumber));
+ selectedItems.delete(id);
+ viewHolder.checkBoxView.setVisibility(View.GONE);
+ viewHolder.quickContactView.setVisibility(View.VISIBLE);
+ updateActionBar();
+ }
+
private static int getVoicemailId(String voicemailUri) {
Assert.checkArgument(voicemailUri != null);
Assert.checkArgument(voicemailUri.length() > 0);
@@ -328,7 +466,7 @@ public class CallLogAdapter extends GroupingListAdapter
* Holds a list of URIs that are pending deletion or undo. If the activity ends before the undo
* timeout, all of the pending URIs will be deleted.
*
- * <p>TODO: move this and OnVoicemailDeletedListener to somewhere like {@link
+ * <p>TODO(twyen): move this and OnVoicemailDeletedListener to somewhere like {@link
* VisualVoicemailCallLogFragment}. The CallLogAdapter does not need to know about what to do with
* hidden item or what to hide.
*/
@@ -358,6 +496,8 @@ public class CallLogAdapter extends GroupingListAdapter
Activity activity,
ViewGroup alertContainer,
CallFetcher callFetcher,
+ MultiSelectRemoveView multiSelectRemoveView,
+ OnActionModeStateChangedListener actionModeStateChangedListener,
CallLogCache callLogCache,
ContactInfoCache contactInfoCache,
VoicemailPlaybackPresenter voicemailPlaybackPresenter,
@@ -367,6 +507,8 @@ public class CallLogAdapter extends GroupingListAdapter
mActivity = activity;
mCallFetcher = callFetcher;
+ mActionModeStateChangedListener = actionModeStateChangedListener;
+ mMultiSelectRemoveView = multiSelectRemoveView;
mVoicemailPlaybackPresenter = voicemailPlaybackPresenter;
if (mVoicemailPlaybackPresenter != null) {
mVoicemailPlaybackPresenter.setOnVoicemailDeletedListener(this);
@@ -426,6 +568,25 @@ public class CallLogAdapter extends GroupingListAdapter
public void onSaveInstanceState(Bundle outState) {
outState.putInt(KEY_EXPANDED_POSITION, mCurrentlyExpandedPosition);
outState.putLong(KEY_EXPANDED_ROW_ID, mCurrentlyExpandedRowId);
+
+ ArrayList<String> listOfSelectedItems = new ArrayList<>();
+
+ if (selectedItems.size() > 0) {
+ for (int i = 0; i < selectedItems.size(); i++) {
+ int id = selectedItems.keyAt(i);
+ String voicemailUri = selectedItems.valueAt(i);
+ LogUtil.i(
+ "CallLogAdapter.onSaveInstanceState", "index %d, id=%d, uri=%s ", i, id, voicemailUri);
+ listOfSelectedItems.add(voicemailUri);
+ }
+ }
+ outState.putStringArrayList(KEY_ACTION_MODE, listOfSelectedItems);
+
+ LogUtil.i(
+ "CallLogAdapter.onSaveInstanceState",
+ "saved: %d, selectedItemsSize:%d",
+ listOfSelectedItems.size(),
+ selectedItems.size());
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
@@ -434,6 +595,33 @@ public class CallLogAdapter extends GroupingListAdapter
savedInstanceState.getInt(KEY_EXPANDED_POSITION, RecyclerView.NO_POSITION);
mCurrentlyExpandedRowId =
savedInstanceState.getLong(KEY_EXPANDED_ROW_ID, NO_EXPANDED_LIST_ITEM);
+ // Restoring multi selected entries
+ ArrayList<String> listOfSelectedItems =
+ savedInstanceState.getStringArrayList(KEY_ACTION_MODE);
+ LogUtil.i(
+ "CallLogAdapter.onRestoreInstanceState",
+ "restored selectedItemsList:%d",
+ listOfSelectedItems.size());
+
+ if (!listOfSelectedItems.isEmpty()) {
+ for (int i = 0; i < listOfSelectedItems.size(); i++) {
+ String voicemailUri = listOfSelectedItems.get(i);
+ int id = getVoicemailId(voicemailUri);
+ LogUtil.i(
+ "CallLogAdapter.onRestoreInstanceState",
+ "restoring selected index %d, id=%d, uri=%s ",
+ i,
+ id,
+ voicemailUri);
+ selectedItems.put(id, voicemailUri);
+ }
+
+ LogUtil.i(
+ "CallLogAdapter.onRestoreInstance",
+ "restored selectedItems %s",
+ selectedItems.toString());
+ updateActionBar();
+ }
}
}
@@ -521,6 +709,7 @@ public class CallLogAdapter extends GroupingListAdapter
mBlockReportSpamListener,
mExpandCollapseListener,
mLongPressListener,
+ mActionModeStateChangedListener,
mCallLogCache,
mCallLogListItemHelper,
mVoicemailPlaybackPresenter);
@@ -546,7 +735,7 @@ public class CallLogAdapter extends GroupingListAdapter
Trace.beginSection("onBindViewHolder: " + position);
switch (getItemViewType(position)) {
case VIEW_TYPE_ALERT:
- //Do nothing
+ // Do nothing
break;
default:
bindCallLogListViewHolder(viewHolder, position);
@@ -559,6 +748,8 @@ public class CallLogAdapter extends GroupingListAdapter
public void onViewRecycled(ViewHolder viewHolder) {
if (viewHolder.getItemViewType() == VIEW_TYPE_CALLLOG) {
CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder;
+ updateCheckMarkedStatusOfEntry(views);
+
if (views.asyncTask != null) {
views.asyncTask.cancel(true);
}
@@ -591,6 +782,8 @@ public class CallLogAdapter extends GroupingListAdapter
return;
}
CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder;
+ updateCheckMarkedStatusOfEntry(views);
+
views.isLoaded = false;
int groupSize = getGroupSize(position);
CallDetailsEntries callDetailsEntries = createCallDetailsEntries(c, groupSize);
@@ -609,6 +802,17 @@ public class CallLogAdapter extends GroupingListAdapter
loadAndRender(views, views.rowId, details, callDetailsEntries);
}
+ private void updateCheckMarkedStatusOfEntry(CallLogListItemViewHolder views) {
+ if (selectedItems.size() > 0 && views.voicemailUri != null) {
+ int id = getVoicemailId(views.voicemailUri);
+ if (selectedItems.get(id) != null) {
+ checkMarkCallLogEntry(views);
+ } else {
+ uncheckMarkCallLogEntry(views, id);
+ }
+ }
+ }
+
private void loadAndRender(
final CallLogListItemViewHolder views,
final long rowId,
@@ -625,12 +829,7 @@ public class CallLogAdapter extends GroupingListAdapter
// the value will be false while capabilities are requested. mExpandCollapseListener will
// attempt to set the field properly in that case
views.isCallComposerCapable = isCallComposerCapable(views.number);
- CallDetailsEntries updatedCallDetailsEntries =
- generateAndMapNewCallDetailsEntriesHistoryResults(
- views.number,
- callDetailsEntries,
- getAllHistoricalData(views.number, callDetailsEntries));
- views.setDetailedPhoneDetails(updatedCallDetailsEntries);
+ views.setDetailedPhoneDetails(callDetailsEntries);
views.lightbringerReady = getLightbringer().isReachable(mActivity, views.number);
final AsyncTask<Void, Void, Boolean> loadDataTask =
new AsyncTask<Void, Void, Boolean>() {
@@ -687,46 +886,7 @@ public class CallLogAdapter extends GroupingListAdapter
getEnrichedCallManager().requestCapabilities(number);
return false;
}
- return capabilities.supportsCallComposer();
- }
-
- @NonNull
- private Map<CallDetailsEntry, List<HistoryResult>> getAllHistoricalData(
- @Nullable String number, @NonNull CallDetailsEntries entries) {
- if (number == null) {
- return Collections.emptyMap();
- }
-
- Map<CallDetailsEntry, List<HistoryResult>> historicalData =
- getEnrichedCallManager().getAllHistoricalData(number, entries);
- if (historicalData == null) {
- getEnrichedCallManager().requestAllHistoricalData(number, entries);
- return Collections.emptyMap();
- }
- return historicalData;
- }
-
- private static CallDetailsEntries generateAndMapNewCallDetailsEntriesHistoryResults(
- @Nullable String number,
- @NonNull CallDetailsEntries callDetailsEntries,
- @NonNull Map<CallDetailsEntry, List<HistoryResult>> mappedResults) {
- if (number == null) {
- return callDetailsEntries;
- }
- CallDetailsEntries.Builder mutableCallDetailsEntries = CallDetailsEntries.newBuilder();
- for (CallDetailsEntry entry : callDetailsEntries.getEntriesList()) {
- CallDetailsEntry.Builder newEntry = CallDetailsEntry.newBuilder().mergeFrom(entry);
- List<HistoryResult> results = mappedResults.get(entry);
- if (results != null) {
- newEntry.addAllHistoryResults(mappedResults.get(entry));
- LogUtil.v(
- "CallLogAdapter.generateAndMapNewCallDetailsEntriesHistoryResults",
- "mapped %d results",
- newEntry.getHistoryResultsList().size());
- }
- mutableCallDetailsEntries.addEntries(newEntry.build());
- }
- return mutableCallDetailsEntries.build();
+ return capabilities.isCallComposerCapable();
}
/**
@@ -744,6 +904,10 @@ public class CallLogAdapter extends GroupingListAdapter
(VERSION.SDK_INT >= VERSION_CODES.N) ? cursor.getString(CallLogQuery.VIA_NUMBER) : "";
final int numberPresentation = cursor.getInt(CallLogQuery.NUMBER_PRESENTATION);
final ContactInfo cachedContactInfo = ContactInfoHelper.getContactInfo(cursor);
+ final int transcriptionState =
+ (VERSION.SDK_INT >= VERSION_CODES.O)
+ ? cursor.getInt(CallLogQuery.TRANSCRIPTION_STATE)
+ : PhoneCallDetailsHelper.TRANSCRIPTION_NOT_STARTED;
final PhoneCallDetails details =
new PhoneCallDetails(number, numberPresentation, postDialDigits);
details.viaNumber = viaNumber;
@@ -753,6 +917,7 @@ public class CallLogAdapter extends GroupingListAdapter
details.features = getCallFeatures(cursor, count);
details.geocode = cursor.getString(CallLogQuery.GEOCODED_LOCATION);
details.transcription = cursor.getString(CallLogQuery.TRANSCRIPTION);
+ details.transcriptionState = transcriptionState;
details.callTypes = getCallTypes(cursor, count);
details.accountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
@@ -785,7 +950,7 @@ public class CallLogAdapter extends GroupingListAdapter
}
@MainThread
- private static CallDetailsEntries createCallDetailsEntries(Cursor cursor, int count) {
+ private CallDetailsEntries createCallDetailsEntries(Cursor cursor, int count) {
Assert.isMainThread();
int position = cursor.getPosition();
CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder();
@@ -798,6 +963,16 @@ public class CallLogAdapter extends GroupingListAdapter
.setDate(cursor.getLong(CallLogQuery.DATE))
.setDuration(cursor.getLong(CallLogQuery.DURATION))
.setFeatures(cursor.getInt(CallLogQuery.FEATURES));
+
+ String phoneAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
+ if (getLightbringer().getPhoneAccountComponentName() != null
+ && getLightbringer()
+ .getPhoneAccountComponentName()
+ .flattenToString()
+ .equals(phoneAccountComponentName)) {
+ entry.setIsLightbringerCall(true);
+ }
+
entries.addEntries(entry.build());
cursor.moveToNext();
}
@@ -840,8 +1015,7 @@ public class CallLogAdapter extends GroupingListAdapter
details.countryIso,
details.cachedContactInfo,
position
- < Bindings.get(mActivity)
- .getConfigProvider()
+ < ConfigProviderBindings.get(mActivity)
.getLong("number_of_call_to_do_remote_lookup", 5L));
}
CharSequence formattedNumber =
@@ -917,6 +1091,12 @@ public class CallLogAdapter extends GroupingListAdapter
views.workIconView.setVisibility(
details.contactUserType == ContactsUtils.USER_TYPE_WORK ? View.VISIBLE : View.GONE);
+ if (selectAllMode && views.voicemailUri != null) {
+ selectedItems.put(getVoicemailId(views.voicemailUri), views.voicemailUri);
+ }
+ if (deselectAllMode && views.voicemailUri != null) {
+ selectedItems.delete(getVoicemailId(views.voicemailUri));
+ }
if (views.voicemailUri != null
&& selectedItems.get(getVoicemailId(views.voicemailUri)) != null) {
views.checkBoxView.setVisibility(View.VISIBLE);
@@ -925,7 +1105,6 @@ public class CallLogAdapter extends GroupingListAdapter
views.checkBoxView.setVisibility(View.GONE);
views.quickContactView.setVisibility(View.VISIBLE);
}
-
mCallLogListItemHelper.setPhoneCallDetails(views, details);
if (mCurrentlyExpandedRowId == views.rowId) {
// In case ViewHolders were added/removed, update the expanded position if the rowIds
@@ -1192,9 +1371,51 @@ public class CallLogAdapter extends GroupingListAdapter
notifyDataSetChanged();
}
+ public void onAllSelected() {
+ selectAllMode = true;
+ deselectAllMode = false;
+ selectedItems.clear();
+ for (int i = 0; i < getItemCount(); i++) {
+ Cursor c = (Cursor) getItem(i);
+ if (c != null) {
+ Assert.checkArgument(CallLogQuery.VOICEMAIL_URI == c.getColumnIndex("voicemail_uri"));
+ String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
+ selectedItems.put(getVoicemailId(voicemailUri), voicemailUri);
+ }
+ }
+ updateActionBar();
+ notifyDataSetChanged();
+ }
+
+ public void onAllDeselected() {
+ selectAllMode = false;
+ deselectAllMode = true;
+ selectedItems.clear();
+ updateActionBar();
+ notifyDataSetChanged();
+ }
+
/** Interface used to initiate a refresh of the content. */
public interface CallFetcher {
void fetchCalls();
}
+
+ /** Interface used to allow single tap multi select for contact photos. */
+ public interface OnActionModeStateChangedListener {
+
+ void onActionModeStateChanged(boolean isEnabled);
+
+ boolean isActionModeStateEnabled();
+ }
+
+ /** Interface used to hide the fragments. */
+ public interface MultiSelectRemoveView {
+
+ void showMultiSelectRemoveView(boolean show);
+
+ void setSelectAllModeToFalse();
+
+ void tapSelectAll();
+ }
}
diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
index a5553d134..78ec7a695 100644
--- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
+++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
@@ -28,6 +28,7 @@ import android.provider.VoicemailContract.Voicemails;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.AsyncTaskExecutor;
import com.android.dialer.common.concurrent.AsyncTaskExecutors;
import com.android.dialer.util.PermissionsUtil;
@@ -45,6 +46,7 @@ public class CallLogAsyncTaskUtil {
public static void markVoicemailAsRead(
@NonNull final Context context, @NonNull final Uri voicemailUri) {
+ LogUtil.enterBlock("CallLogAsyncTaskUtil.markVoicemailAsRead, voicemailUri: " + voicemailUri);
if (sAsyncTaskExecutor == null) {
initTaskExecutor();
}
@@ -64,11 +66,8 @@ public class CallLogAsyncTaskUtil {
.update(voicemailUri, values, Voicemails.IS_READ + " = 0", null)
> 0) {
uploadVoicemailLocalChangesToServer(context);
+ CallLogNotificationsService.markAllNewVoicemailsAsOld(context);
}
-
- Intent intent = new Intent(context, CallLogNotificationsService.class);
- intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
- context.startService(intent);
return null;
}
});
@@ -110,7 +109,8 @@ public class CallLogAsyncTaskUtil {
}
public static void markCallAsRead(@NonNull final Context context, @NonNull final long[] callIds) {
- if (!PermissionsUtil.hasPhonePermissions(context)) {
+ if (!PermissionsUtil.hasPhonePermissions(context)
+ || !PermissionsUtil.hasCallLogWritePermissions(context)) {
return;
}
if (sAsyncTaskExecutor == null) {
diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java
index 6e4b23fc1..6d4aea91f 100644
--- a/java/com/android/dialer/app/calllog/CallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/CallLogFragment.java
@@ -20,7 +20,6 @@ import static android.Manifest.permission.READ_CALL_LOG;
import android.app.Activity;
import android.app.Fragment;
-import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -35,53 +34,68 @@ import android.provider.ContactsContract;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.v13.app.FragmentCompat;
+import android.support.v13.app.FragmentCompat.OnRequestPermissionsResultCallback;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
import com.android.dialer.app.Bindings;
import com.android.dialer.app.R;
+import com.android.dialer.app.calllog.CallLogAdapter.CallFetcher;
+import com.android.dialer.app.calllog.CallLogAdapter.MultiSelectRemoveView;
import com.android.dialer.app.calllog.calllogcache.CallLogCache;
import com.android.dialer.app.contactinfo.ContactInfoCache;
import com.android.dialer.app.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
import com.android.dialer.app.contactinfo.ExpirableCacheHeadlessFragment;
import com.android.dialer.app.list.ListsFragment;
import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
-import com.android.dialer.app.widget.EmptyContentView;
-import com.android.dialer.app.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.database.CallLogQueryHandler;
+import com.android.dialer.database.CallLogQueryHandler.Listener;
import com.android.dialer.location.GeoUtil;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.oem.CequintCallerIdManager;
+import com.android.dialer.performancereport.PerformanceReport;
import com.android.dialer.phonenumbercache.ContactInfoHelper;
import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.widget.EmptyContentView;
+import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
+import java.util.Arrays;
/**
* Displays a list of call log entries. To filter for a particular kind of call (all, missed or
* voicemails), specify it in the constructor.
*/
public class CallLogFragment extends Fragment
- implements CallLogQueryHandler.Listener,
- CallLogAdapter.CallFetcher,
+ implements Listener,
+ CallFetcher,
+ MultiSelectRemoveView,
OnEmptyViewActionButtonClickedListener,
- FragmentCompat.OnRequestPermissionsResultCallback,
- CallLogModalAlertManager.Listener {
+ OnRequestPermissionsResultCallback,
+ CallLogModalAlertManager.Listener,
+ OnClickListener {
private static final String KEY_FILTER_TYPE = "filter_type";
private static final String KEY_LOG_LIMIT = "log_limit";
private static final String KEY_DATE_LIMIT = "date_limit";
private static final String KEY_IS_CALL_LOG_ACTIVITY = "is_call_log_activity";
private static final String KEY_HAS_READ_CALL_LOG_PERMISSION = "has_read_call_log_permission";
private static final String KEY_REFRESH_DATA_REQUIRED = "refresh_data_required";
+ private static final String KEY_SELECT_ALL_MODE = "select_all_mode_checked";
// No limit specified for the number of logs to show; use the CallLogQueryHandler's default.
private static final int NO_LOG_LIMIT = -1;
// No date-based filtering.
private static final int NO_DATE_LIMIT = 0;
- private static final int READ_CALL_LOG_PERMISSION_REQUEST_CODE = 1;
+ private static final int PHONE_PERMISSIONS_REQUEST_CODE = 1;
private static final int EVENT_UPDATE_DISPLAY = 1;
@@ -90,13 +104,15 @@ public class CallLogFragment extends Fragment
// See issue 6363009
private final ContentObserver mCallLogObserver = new CustomContentObserver();
private final ContentObserver mContactsObserver = new CustomContentObserver();
+ private View mMultiSelectUnSelectAllViewContent;
+ private TextView mSelectUnselectAllViewText;
+ private ImageView mSelectUnselectAllIcon;
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private CallLogAdapter mAdapter;
private CallLogQueryHandler mCallLogQueryHandler;
private boolean mScrollToTop;
private EmptyContentView mEmptyListView;
- private KeyguardManager mKeyguardManager;
private ContactInfoCache mContactInfoCache;
private final OnContactInfoChangedListener mOnContactInfoChangedListener =
new OnContactInfoChangedListener() {
@@ -123,6 +139,7 @@ public class CallLogFragment extends Fragment
* True if this instance of the CallLogFragment shown in the CallLogActivity.
*/
private boolean mIsCallLogActivity = false;
+ private boolean selectAllMode;
private final Handler mDisplayUpdateHandler =
new Handler() {
@Override
@@ -194,12 +211,12 @@ public class CallLogFragment extends Fragment
mIsCallLogActivity = state.getBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity);
mHasReadCallLogPermission = state.getBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, false);
mRefreshDataRequired = state.getBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired);
+ selectAllMode = state.getBoolean(KEY_SELECT_ALL_MODE, false);
}
final Activity activity = getActivity();
final ContentResolver resolver = activity.getContentResolver();
mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);
- mKeyguardManager = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
if (PermissionsUtil.hasCallLogReadPermissions(getContext())) {
resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver);
@@ -290,12 +307,20 @@ public class CallLogFragment extends Fragment
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
+ PerformanceReport.logOnScrollStateChange(mRecyclerView);
mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view);
mEmptyListView.setImage(R.drawable.empty_call_log);
mEmptyListView.setActionClickedListener(this);
mModalAlertView = (ViewGroup) view.findViewById(R.id.modal_message_container);
mModalAlertManager =
new CallLogModalAlertManager(LayoutInflater.from(getContext()), mModalAlertView, this);
+ mMultiSelectUnSelectAllViewContent =
+ view.findViewById(R.id.multi_select_select_all_view_content);
+ mSelectUnselectAllViewText = (TextView) view.findViewById(R.id.select_all_view_text);
+ mSelectUnselectAllIcon = (ImageView) view.findViewById(R.id.select_all_view_icon);
+ mMultiSelectUnSelectAllViewContent.setOnClickListener(null);
+ mSelectUnselectAllIcon.setOnClickListener(this);
+ mSelectUnselectAllViewText.setOnClickListener(this);
}
protected void setupData() {
@@ -317,7 +342,11 @@ public class CallLogFragment extends Fragment
getActivity(),
mRecyclerView,
this,
- CallLogCache.getCallLogCache(getActivity()),
+ this,
+ activityType == CallLogAdapter.ACTIVITY_TYPE_DIALTACTS
+ ? (CallLogAdapter.OnActionModeStateChangedListener) getActivity()
+ : null,
+ new CallLogCache(getActivity()),
mContactInfoCache,
getVoicemailPlaybackPresenter(),
new FilteredNumberAsyncQueryHandler(getActivity()),
@@ -335,9 +364,18 @@ public class CallLogFragment extends Fragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setupData();
+ updateSelectAllState(savedInstanceState);
mAdapter.onRestoreInstanceState(savedInstanceState);
}
+ private void updateSelectAllState(Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ if (savedInstanceState.getBoolean(KEY_SELECT_ALL_MODE, false)) {
+ updateSelectAllIcon();
+ }
+ }
+ }
+
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -380,9 +418,17 @@ public class CallLogFragment extends Fragment
}
@Override
- public void onStop() {
- updateOnTransition();
+ public void onStart() {
+ super.onStart();
+ CequintCallerIdManager cequintCallerIdManager = null;
+ if (CequintCallerIdManager.isCequintCallerIdEnabled(getContext())) {
+ cequintCallerIdManager = CequintCallerIdManager.createInstanceForCallLog();
+ }
+ mContactInfoCache.setCequintCallerIdManager(cequintCallerIdManager);
+ }
+ @Override
+ public void onStop() {
super.onStop();
mAdapter.onStop();
mContactInfoCache.stop();
@@ -407,7 +453,7 @@ public class CallLogFragment extends Fragment
outState.putBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity);
outState.putBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, mHasReadCallLogPermission);
outState.putBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired);
-
+ outState.putBoolean(KEY_SELECT_ALL_MODE, selectAllMode);
mAdapter.onSaveInstanceState(outState);
}
@@ -451,6 +497,8 @@ public class CallLogFragment extends Fragment
mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL);
} else if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) {
mEmptyListView.setActionLabel(R.string.call_log_all_empty_action);
+ } else {
+ mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL);
}
}
@@ -463,9 +511,7 @@ public class CallLogFragment extends Fragment
super.setMenuVisibility(menuVisible);
if (mMenuVisible != menuVisible) {
mMenuVisible = menuVisible;
- if (!menuVisible) {
- updateOnTransition();
- } else if (isResumed()) {
+ if (menuVisible && isResumed()) {
refreshData();
}
}
@@ -483,7 +529,6 @@ public class CallLogFragment extends Fragment
fetchCalls();
mCallLogQueryHandler.fetchVoicemailStatus();
mCallLogQueryHandler.fetchMissedCallsUnreadCount();
- updateOnTransition();
mRefreshDataRequired = false;
} else {
// Refresh the display of the existing data to update the timestamp text descriptions.
@@ -491,22 +536,6 @@ public class CallLogFragment extends Fragment
}
}
- /**
- * Updates the voicemail notification state.
- *
- * <p>TODO: Move to CallLogActivity
- */
- private void updateOnTransition() {
- // We don't want to update any call data when keyguard is on because the user has likely not
- // seen the new calls yet.
- // This might be called before onCreate() and thus we need to check null explicitly.
- if (mKeyguardManager != null
- && !mKeyguardManager.inKeyguardRestrictedInputMode()
- && mCallTypeFilter == Calls.VOICEMAIL_TYPE) {
- CallLogNotificationsService.markNewVoicemailsAsOld(getActivity(), null);
- }
- }
-
@Override
public void onEmptyViewActionButtonClicked() {
final Activity activity = getActivity();
@@ -514,9 +543,14 @@ public class CallLogFragment extends Fragment
return;
}
- if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) {
- FragmentCompat.requestPermissions(
- this, new String[] {READ_CALL_LOG}, READ_CALL_LOG_PERMISSION_REQUEST_CODE);
+ String[] deniedPermissions =
+ PermissionsUtil.getPermissionsCurrentlyDenied(
+ getContext(), PermissionsUtil.allPhoneGroupPermissionsUsedInDialer);
+ if (deniedPermissions.length > 0) {
+ LogUtil.i(
+ "CallLogFragment.onEmptyViewActionButtonClicked",
+ "Requesting permissions: " + Arrays.toString(deniedPermissions));
+ FragmentCompat.requestPermissions(this, deniedPermissions, PHONE_PERMISSIONS_REQUEST_CODE);
} else if (!mIsCallLogActivity) {
// Show dialpad if we are not in the call log activity.
((HostInterface) activity).showDialpad();
@@ -526,7 +560,7 @@ public class CallLogFragment extends Fragment
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
- if (requestCode == READ_CALL_LOG_PERMISSION_REQUEST_CODE) {
+ if (requestCode == PHONE_PERMISSIONS_REQUEST_CODE) {
if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
// Force a refresh of the data since we were missing the permission before this.
mRefreshDataRequired = true;
@@ -589,6 +623,51 @@ public class CallLogFragment extends Fragment
}
}
+ @Override
+ public void showMultiSelectRemoveView(boolean show) {
+ mMultiSelectUnSelectAllViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
+ mMultiSelectUnSelectAllViewContent.setAlpha(show ? 0 : 1);
+ mMultiSelectUnSelectAllViewContent.animate().alpha(show ? 1 : 0).start();
+ ((ListsFragment) getParentFragment()).showMultiSelectRemoveView(show);
+ }
+
+ @Override
+ public void setSelectAllModeToFalse() {
+ selectAllMode = false;
+ mSelectUnselectAllIcon.setImageDrawable(
+ getContext().getDrawable(R.drawable.ic_empty_check_mark_white_24dp));
+ }
+
+ @Override
+ public void tapSelectAll() {
+ LogUtil.i("CallLogFragment.tapSelectAll", "imitating select all");
+ selectAllMode = true;
+ updateSelectAllIcon();
+ }
+
+ @Override
+ public void onClick(View v) {
+ selectAllMode = !selectAllMode;
+ if (selectAllMode) {
+ Logger.get(v.getContext()).logImpression(DialerImpression.Type.MULTISELECT_SELECT_ALL);
+ } else {
+ Logger.get(v.getContext()).logImpression(DialerImpression.Type.MULTISELECT_UNSELECT_ALL);
+ }
+ updateSelectAllIcon();
+ }
+
+ private void updateSelectAllIcon() {
+ if (selectAllMode) {
+ mSelectUnselectAllIcon.setImageDrawable(
+ getContext().getDrawable(R.drawable.ic_check_mark_blue_24dp));
+ getAdapter().onAllSelected();
+ } else {
+ mSelectUnselectAllIcon.setImageDrawable(
+ getContext().getDrawable(R.drawable.ic_empty_check_mark_white_24dp));
+ getAdapter().onAllDeselected();
+ }
+ }
+
public interface HostInterface {
void showDialpad();
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 1daccd1a4..60ed7dd09 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -17,40 +17,45 @@
package com.android.dialer.app.calllog;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Bundle;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
import android.view.ContextMenu;
+import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewStub;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.QuickContactBadge;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.contacts.common.ClipboardUtils;
-import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
import com.android.contacts.common.dialog.CallSubjectDialog;
-import com.android.contacts.common.util.UriUtils;
import com.android.dialer.app.DialtactsActivity;
import com.android.dialer.app.R;
+import com.android.dialer.app.calllog.CallLogAdapter.OnActionModeStateChangedListener;
import com.android.dialer.app.calllog.calllogcache.CallLogCache;
import com.android.dialer.app.voicemail.VoicemailPlaybackLayout;
import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
@@ -58,23 +63,39 @@ import com.android.dialer.blocking.BlockedNumbersMigrator;
import com.android.dialer.blocking.FilteredNumberCompat;
import com.android.dialer.blocking.FilteredNumbersUtil;
import com.android.dialer.callcomposer.CallComposerActivity;
-import com.android.dialer.callcomposer.CallComposerContact;
+import com.android.dialer.calldetails.CallDetailsActivity;
import com.android.dialer.calldetails.CallDetailsEntries;
-import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.compat.CompatUtils;
+import com.android.dialer.compat.telephony.TelephonyManagerCompat;
+import com.android.dialer.configprovider.ConfigProviderBindings;
+import com.android.dialer.constants.ActivityRequestCodes;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.dialercontact.DialerContact;
+import com.android.dialer.dialercontact.SimDetails;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.lettertile.LetterTileDrawable.ContactType;
import com.android.dialer.lightbringer.Lightbringer;
import com.android.dialer.lightbringer.LightbringerComponent;
import com.android.dialer.logging.ContactSource;
import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.InteractionEvent;
import com.android.dialer.logging.Logger;
import com.android.dialer.logging.ScreenEvent;
+import com.android.dialer.logging.UiAction;
+import com.android.dialer.performancereport.PerformanceReport;
import com.android.dialer.phonenumbercache.CachedNumberLookupService;
import com.android.dialer.phonenumbercache.ContactInfo;
import com.android.dialer.phonenumbercache.PhoneNumberCache;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
+import com.android.dialer.telecom.TelecomUtil;
import com.android.dialer.util.CallUtil;
import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.UriUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* This is an object containing references to views contained by the call log list item. This
@@ -90,7 +111,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
/** The root view of the call log list item */
public final View rootView;
/** The quick contact badge for the contact. */
- public final QuickContactBadge quickContactView;
+ public final DialerQuickContactBadge quickContactView;
/** The primary action view of the entry. */
public final View primaryActionView;
/** The details of the phone call. */
@@ -103,11 +124,13 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
public final ImageView primaryActionButtonView;
private final Context mContext;
+ @Nullable private final PhoneAccountHandle mDefaultPhoneAccountHandle;
private final CallLogCache mCallLogCache;
private final CallLogListItemHelper mCallLogListItemHelper;
private final CachedNumberLookupService mCachedNumberLookupService;
private final VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
private final OnClickListener mBlockReportListener;
+ @HostUi private final int hostUi;
/** Whether the data fields are populated by the worker thread, ready to be shown. */
public boolean isLoaded;
/** The view containing call log item actions. Null until the ViewStub is inflated. */
@@ -144,7 +167,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
* The callable phone number for the current call log entry. Cached here as the call back intent
* is set only when the actions ViewStub is inflated.
*/
- public String number;
+ @Nullable public String number;
/** The post-dial numbers that are dialed following the phone number. */
public String postDialDigits;
/** The formatted phone number to display. */
@@ -201,6 +224,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
public boolean lightbringerReady;
private View.OnClickListener mExpandCollapseListener;
+ private final OnActionModeStateChangedListener onActionModeStateChangedListener;
private final View.OnLongClickListener longPressListener;
private boolean mVoicemailPrimaryActionButtonClicked;
@@ -216,11 +240,12 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
OnClickListener blockReportListener,
View.OnClickListener expandCollapseListener,
View.OnLongClickListener longClickListener,
+ CallLogAdapter.OnActionModeStateChangedListener actionModeStateChangedListener,
CallLogCache callLogCache,
CallLogListItemHelper callLogListItemHelper,
VoicemailPlaybackPresenter voicemailPlaybackPresenter,
View rootView,
- QuickContactBadge quickContactView,
+ DialerQuickContactBadge dialerQuickContactView,
View primaryActionView,
PhoneCallDetailsViews phoneCallDetailsViews,
CardView callLogEntryView,
@@ -230,6 +255,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
mContext = context;
mExpandCollapseListener = expandCollapseListener;
+ onActionModeStateChangedListener = actionModeStateChangedListener;
longPressListener = longClickListener;
mCallLogCache = callLogCache;
mCallLogListItemHelper = callLogListItemHelper;
@@ -237,8 +263,12 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
mBlockReportListener = blockReportListener;
mCachedNumberLookupService = PhoneNumberCache.get(mContext).getCachedNumberLookupService();
+ // Cache this to avoid having to look it up each time we bind to a call log entry
+ mDefaultPhoneAccountHandle =
+ TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL);
+
this.rootView = rootView;
- this.quickContactView = quickContactView;
+ this.quickContactView = dialerQuickContactView;
this.primaryActionView = primaryActionView;
this.phoneCallDetailsViews = phoneCallDetailsViews;
this.callLogEntryView = callLogEntryView;
@@ -251,6 +281,23 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
phoneCallDetailsViews.nameView.setElegantTextHeight(false);
phoneCallDetailsViews.callLocationAndDate.setElegantTextHeight(false);
+ if (mContext instanceof CallLogActivity) {
+ hostUi = HostUi.CALL_HISTORY;
+ Logger.get(mContext)
+ .logQuickContactOnTouch(
+ quickContactView, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_HISTORY, true);
+ } else if (mVoicemailPlaybackPresenter == null) {
+ hostUi = HostUi.CALL_LOG;
+ Logger.get(mContext)
+ .logQuickContactOnTouch(
+ quickContactView, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CALL_LOG, true);
+ } else {
+ hostUi = HostUi.VOICEMAIL;
+ Logger.get(mContext)
+ .logQuickContactOnTouch(
+ quickContactView, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_VOICEMAIL, false);
+ }
+
quickContactView.setOverlay(null);
if (CompatUtils.hasPrioritizedMimeType()) {
quickContactView.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
@@ -264,6 +311,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
CallLogAdapter.ENABLE_CALL_LOG_MULTI_SELECT_FLAG)) {
primaryActionView.setOnLongClickListener(longPressListener);
quickContactView.setOnLongClickListener(longPressListener);
+ quickContactView.setMulitSelectListeners(
+ mExpandCollapseListener, onActionModeStateChangedListener);
} else {
primaryActionView.setOnCreateContextMenuListener(this);
}
@@ -275,6 +324,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
OnClickListener blockReportListener,
View.OnClickListener expandCollapseListener,
View.OnLongClickListener longClickListener,
+ CallLogAdapter.OnActionModeStateChangedListener actionModeStateChangeListener,
CallLogCache callLogCache,
CallLogListItemHelper callLogListItemHelper,
VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
@@ -284,11 +334,12 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
blockReportListener,
expandCollapseListener,
longClickListener,
+ actionModeStateChangeListener,
callLogCache,
callLogListItemHelper,
voicemailPlaybackPresenter,
view,
- (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
+ (DialerQuickContactBadge) view.findViewById(R.id.quick_contact_photo),
view.findViewById(R.id.primary_action_view),
PhoneCallDetailsViews.fromView(view),
(CardView) view.findViewById(R.id.call_log_row),
@@ -297,8 +348,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
}
public static CallLogListItemViewHolder createForTest(Context context) {
+ return createForTest(context, null, null);
+ }
+
+ public static CallLogListItemViewHolder createForTest(
+ Context context,
+ View.OnClickListener expandCollapseListener,
+ VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
Resources resources = context.getResources();
- CallLogCache callLogCache = CallLogCache.getCallLogCache(context);
+ CallLogCache callLogCache = new CallLogCache(context);
PhoneCallDetailsHelper phoneCallDetailsHelper =
new PhoneCallDetailsHelper(context, resources, callLogCache);
@@ -306,13 +364,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
new CallLogListItemViewHolder(
context,
null,
- null /* expandCollapseListener */,
+ expandCollapseListener /* expandCollapseListener */,
+ null,
null,
callLogCache,
new CallLogListItemHelper(phoneCallDetailsHelper, resources, callLogCache),
- null /* voicemailPlaybackPresenter */,
- new View(context),
- new QuickContactBadge(context),
+ voicemailPlaybackPresenter,
+ LayoutInflater.from(context).inflate(R.layout.call_log_list_item, null),
+ new DialerQuickContactBadge(context),
new View(context),
PhoneCallDetailsViews.createForTest(context),
new CardView(context),
@@ -456,6 +515,18 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
// Treat as normal list item; show call button, if possible.
if (PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation)) {
boolean isVoicemailNumber = mCallLogCache.isVoicemailNumber(accountHandle, number);
+
+ if (!isVoicemailNumber && showLightbringerPrimaryButton()) {
+ CallIntentBuilder.increaseLightbringerCallButtonAppearInCollapsedCallLogItemCount();
+ primaryActionButtonView.setTag(IntentProvider.getLightbringerIntentProvider(number));
+ primaryActionButtonView.setContentDescription(
+ TextUtils.expandTemplate(
+ mContext.getString(R.string.description_video_call_action), validNameOrNumber));
+ primaryActionButtonView.setImageResource(R.drawable.quantum_ic_videocam_vd_theme_24);
+ primaryActionButtonView.setVisibility(View.VISIBLE);
+ return;
+ }
+
if (isVoicemailNumber) {
// Call to generic voicemail number, in case there are multiple accounts.
primaryActionButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider());
@@ -467,7 +538,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
primaryActionButtonView.setContentDescription(
TextUtils.expandTemplate(
mContext.getString(R.string.description_call_action), validNameOrNumber));
- primaryActionButtonView.setImageResource(R.drawable.quantum_ic_call_white_24);
+ primaryActionButtonView.setImageResource(R.drawable.quantum_ic_call_vd_theme_24);
primaryActionButtonView.setVisibility(View.VISIBLE);
} else {
primaryActionButtonView.setTag(null);
@@ -483,12 +554,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
private void bindActionButtons() {
boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation);
+ // Hide the call buttons by default. We then set it to be visible when appropriate below.
+ // This saves us having to remember to set it to GONE in multiple places.
+ callButtonView.setVisibility(View.GONE);
+ videoCallButtonView.setVisibility(View.GONE);
+
if (isFullyUndialableVoicemail()) {
// Sometimes the voicemail server will report the message is from some non phone number
// source. If the number does not contains any dialable digit treat it as it is from a unknown
// number, remove all action buttons but still show the voicemail playback layout.
- callButtonView.setVisibility(View.GONE);
- videoCallButtonView.setVisibility(View.GONE);
detailsButtonView.setVisibility(View.GONE);
createNewContactButtonView.setVisibility(View.GONE);
addToExistingContactButtonView.setVisibility(View.GONE);
@@ -513,34 +587,40 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
return;
}
- if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) {
+ TextView callTypeOrLocationView =
+ ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text));
+
+ if (canPlaceCallToNumber) {
callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
+ callTypeOrLocationView.setVisibility(View.GONE);
+ }
+
+ if (!TextUtils.isEmpty(voicemailUri) && canPlaceCallToNumber) {
((TextView) callButtonView.findViewById(R.id.call_action_text))
.setText(
TextUtils.expandTemplate(
mContext.getString(R.string.call_log_action_call),
nameOrNumber == null ? "" : nameOrNumber));
- TextView callTypeOrLocationView =
- ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text));
+
if (callType == Calls.VOICEMAIL_TYPE && !TextUtils.isEmpty(callTypeOrLocation)) {
callTypeOrLocationView.setText(callTypeOrLocation);
callTypeOrLocationView.setVisibility(View.VISIBLE);
- } else {
- callTypeOrLocationView.setVisibility(View.GONE);
}
callButtonView.setVisibility(View.VISIBLE);
- } else {
- callButtonView.setVisibility(View.GONE);
}
- if (hasPlacedCarrierVideoCall() || canSupportCarrierVideoCall()) {
+ // We need to check if we are showing the Lightbringer primary button. If we are, then we should
+ // show the "Call" button here regardless of IMS availability.
+ if (showLightbringerPrimaryButton()) {
+ callButtonView.setVisibility(View.VISIBLE);
+ videoCallButtonView.setVisibility(View.GONE);
+ } else if (CallUtil.isVideoEnabled(mContext)
+ && (hasPlacedCarrierVideoCall() || canSupportCarrierVideoCall())) {
videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
videoCallButtonView.setVisibility(View.VISIBLE);
} else if (lightbringerReady) {
videoCallButtonView.setTag(IntentProvider.getLightbringerIntentProvider(number));
videoCallButtonView.setVisibility(View.VISIBLE);
- } else {
- videoCallButtonView.setVisibility(View.GONE);
}
// For voicemail calls, show the voicemail playback layout; hide otherwise.
@@ -567,8 +647,12 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
detailsButtonView.setVisibility(View.GONE);
} else {
detailsButtonView.setVisibility(View.VISIBLE);
+ boolean canReportCallerId =
+ mCachedNumberLookupService != null
+ && mCachedNumberLookupService.canReportAsInvalid(info.sourceType, info.objectId);
detailsButtonView.setTag(
- IntentProvider.getCallDetailIntentProvider(callDetailsEntries, buildContact()));
+ IntentProvider.getCallDetailIntentProvider(
+ callDetailsEntries, buildContact(), canReportCallerId));
}
boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam);
@@ -616,6 +700,12 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
return false;
}
+ private boolean showLightbringerPrimaryButton() {
+ return accountHandle != null
+ && accountHandle.getComponentName().equals(getLightbringer().getPhoneAccountComponentName())
+ && lightbringerReady;
+ }
+
private static boolean hasDialableChar(CharSequence number) {
if (TextUtils.isEmpty(number)) {
return false;
@@ -635,12 +725,10 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
if (accountHandle == null) {
return false;
}
- if (accountHandle
- .getComponentName()
- .equals(getLightbringer().getPhoneAccountComponentName(mContext))) {
+ if (mDefaultPhoneAccountHandle == null) {
return false;
}
- return true;
+ return accountHandle.getComponentName().equals(mDefaultPhoneAccountHandle.getComponentName());
}
private boolean canSupportCarrierVideoCall() {
@@ -690,12 +778,23 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
return;
}
- final TextView view = phoneCallDetailsViews.voicemailTranscriptionView;
- if (!isExpanded || TextUtils.isEmpty(view.getText())) {
- view.setVisibility(View.GONE);
+ View transcriptContainerView = phoneCallDetailsViews.transcriptionView;
+ TextView transcriptView = phoneCallDetailsViews.voicemailTranscriptionView;
+ TextView transcriptBrandingView = phoneCallDetailsViews.voicemailTranscriptionBrandingView;
+ if (TextUtils.isEmpty(transcriptView.getText())) {
+ Assert.checkArgument(TextUtils.isEmpty(transcriptBrandingView.getText()));
+ }
+ if (!isExpanded || TextUtils.isEmpty(transcriptView.getText())) {
+ transcriptContainerView.setVisibility(View.GONE);
return;
}
- view.setVisibility(View.VISIBLE);
+ transcriptContainerView.setVisibility(View.VISIBLE);
+ transcriptView.setVisibility(View.VISIBLE);
+ if (TextUtils.isEmpty(transcriptBrandingView.getText())) {
+ phoneCallDetailsViews.voicemailTranscriptionBrandingView.setVisibility(View.GONE);
+ } else {
+ phoneCallDetailsViews.voicemailTranscriptionBrandingView.setVisibility(View.VISIBLE);
+ }
}
public void updatePhoto() {
@@ -717,19 +816,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
getContactType());
}
- private int getContactType() {
- int contactType = ContactPhotoManager.TYPE_DEFAULT;
- if (mCallLogCache.isVoicemailNumber(accountHandle, number)) {
- contactType = ContactPhotoManager.TYPE_VOICEMAIL;
- } else if (isSpam) {
- contactType = ContactPhotoManager.TYPE_SPAM;
- } else if (mCachedNumberLookupService != null
- && mCachedNumberLookupService.isBusiness(info.sourceType)) {
- contactType = ContactPhotoManager.TYPE_BUSINESS;
- } else if (numberPresentation == TelecomManager.PRESENTATION_RESTRICTED) {
- contactType = ContactPhotoManager.TYPE_GENERIC_AVATAR;
- }
- return contactType;
+ private @ContactType int getContactType() {
+ return LetterTileDrawable.getContactTypeFromPrimitives(
+ mCallLogCache.isVoicemailNumber(accountHandle, number),
+ isSpam,
+ mCachedNumberLookupService != null
+ && mCachedNumberLookupService.isBusiness(info.sourceType),
+ numberPresentation,
+ false);
}
@Override
@@ -789,25 +883,67 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
Activity activity = (Activity) mContext;
activity.startActivityForResult(
CallComposerActivity.newIntent(activity, buildContact()),
- DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_COMPOSE);
+ ActivityRequestCodes.DIALTACTS_CALL_COMPOSER);
} else if (view.getId() == R.id.share_voicemail) {
Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED);
mVoicemailPlaybackPresenter.shareVoicemail();
} else {
logCallLogAction(view.getId());
+
final IntentProvider intentProvider = (IntentProvider) view.getTag();
- if (intentProvider != null) {
- final Intent intent = intentProvider.getIntent(mContext);
- // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
- if (intent != null) {
- DialerUtils.startActivityWithErrorToast(mContext, intent);
+ if (intentProvider == null) {
+ return;
+ }
+
+ final Intent intent = intentProvider.getIntent(mContext);
+ // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
+ if (intent == null) {
+ return;
+ }
+
+ if (info != null && info.lookupKey != null) {
+ Bundle extras = new Bundle();
+ if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
+ extras = intent.getParcelableExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
}
+ extras.putBoolean(TelephonyManagerCompat.ALLOW_ASSISTED_DIAL, true);
+ intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
}
+
+ // We check to see if we are starting a Lightbringer intent. The reason is Lightbringer
+ // intents need to be started using startActivityForResult instead of the usual startActivity
+ String packageName = intent.getPackage();
+ if (packageName != null && packageName.equals(getLightbringer().getPackageName())) {
+ Logger.get(mContext)
+ .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG);
+ startLightbringerActivity(intent);
+ } else if (CallDetailsActivity.isLaunchIntent(intent)) {
+ PerformanceReport.recordClick(UiAction.Type.OPEN_CALL_DETAIL);
+ ((Activity) mContext)
+ .startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_CALL_DETAILS);
+ } else {
+ if (Intent.ACTION_CALL.equals(intent.getAction())
+ && intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, -1)
+ == VideoProfile.STATE_BIDIRECTIONAL) {
+ Logger.get(mContext)
+ .logImpression(DialerImpression.Type.IMS_VIDEO_REQUESTED_FROM_CALL_LOG);
+ }
+ DialerUtils.startActivityWithErrorToast(mContext, intent);
+ }
+ }
+ }
+
+ private void startLightbringerActivity(Intent intent) {
+ try {
+ Activity activity = (Activity) mContext;
+ activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_LIGHTBRINGER);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(mContext, R.string.activity_not_available, Toast.LENGTH_SHORT).show();
}
}
- private CallComposerContact buildContact() {
- CallComposerContact.Builder contact = CallComposerContact.newBuilder();
+ private DialerContact buildContact() {
+ DialerContact.Builder contact = DialerContact.newBuilder();
contact.setPhotoId(info.photoId);
if (info.photoUri != null) {
contact.setPhotoUri(info.photoUri.toString());
@@ -819,13 +955,23 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
contact.setNameOrNumber((String) nameOrNumber);
}
contact.setContactType(getContactType());
- contact.setNumber(number);
+ if (number != null) {
+ contact.setNumber(number);
+ }
/* second line of contact view. */
if (!TextUtils.isEmpty(info.name)) {
contact.setDisplayNumber(displayNumber);
}
/* phone number type (e.g. mobile) in second line of contact view */
contact.setNumberLabel(numberType);
+
+ /* third line of contact view. */
+ String accountLabel = mCallLogCache.getAccountLabel(accountHandle);
+ if (!TextUtils.isEmpty(accountLabel)) {
+ SimDetails.Builder simDetails = SimDetails.newBuilder().setNetwork(accountLabel);
+ simDetails.setColor(mCallLogCache.getAccountColor(accountHandle));
+ contact.setSimDetails(simDetails.build());
+ }
return contact.build();
}
@@ -834,8 +980,38 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SEND_MESSAGE);
} else if (id == R.id.add_to_existing_contact_action) {
Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_ADD_TO_CONTACT);
+ switch (hostUi) {
+ case HostUi.CALL_HISTORY:
+ Logger.get(mContext)
+ .logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_CALL_HISTORY);
+ break;
+ case HostUi.CALL_LOG:
+ Logger.get(mContext).logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_CALL_LOG);
+ break;
+ case HostUi.VOICEMAIL:
+ Logger.get(mContext).logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_VOICEMAIL);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException();
+ }
} else if (id == R.id.create_new_contact_action) {
Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_CREATE_NEW_CONTACT);
+ switch (hostUi) {
+ case HostUi.CALL_HISTORY:
+ Logger.get(mContext)
+ .logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_CALL_HISTORY);
+ break;
+ case HostUi.CALL_LOG:
+ Logger.get(mContext)
+ .logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_CALL_LOG);
+ break;
+ case HostUi.VOICEMAIL:
+ Logger.get(mContext)
+ .logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_VOICEMAIL);
+ break;
+ default:
+ throw Assert.createIllegalStateFailException();
+ }
}
}
@@ -987,6 +1163,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder
Logger.get(mContext).logScreenView(ScreenEvent.Type.CALL_LOG_CONTEXT_MENU, (Activity) mContext);
}
+ /** Specifies where the view holder belongs. */
+ @IntDef({HostUi.CALL_LOG, HostUi.CALL_HISTORY, HostUi.VOICEMAIL})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface HostUi {
+ int CALL_LOG = 0;
+ int CALL_HISTORY = 1;
+ int VOICEMAIL = 2;
+ }
+
public interface OnClickListener {
void onBlockReportSpam(
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java
index e169b8de9..43e03e9fd 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java
@@ -18,7 +18,6 @@ package com.android.dialer.app.calllog;
import android.Manifest;
import android.annotation.TargetApi;
-import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -27,6 +26,7 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
import android.provider.CallLog.Calls;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.support.v4.os.UserManagerCompat;
@@ -36,7 +36,6 @@ import com.android.dialer.app.R;
import com.android.dialer.calllogutils.PhoneNumberDisplayUtil;
import com.android.dialer.common.LogUtil;
import com.android.dialer.location.GeoUtil;
-import com.android.dialer.notification.GroupedNotificationUtil;
import com.android.dialer.phonenumbercache.ContactInfo;
import com.android.dialer.phonenumbercache.ContactInfoHelper;
import com.android.dialer.util.PermissionsUtil;
@@ -46,7 +45,6 @@ import java.util.List;
/** Helper class operating on call log notifications. */
public class CallLogNotificationsQueryHelper {
- private static final String TAG = "CallLogNotifHelper";
private final Context mContext;
private final NewCallsQuery mNewCallsQuery;
private final ContactInfoHelper mContactInfoHelper;
@@ -74,44 +72,58 @@ public class CallLogNotificationsQueryHelper {
countryIso);
}
+ public static void markAllMissedCallsInCallLogAsRead(@NonNull Context context) {
+ markMissedCallsInCallLogAsRead(context, null);
+ }
+
+ public static void markSingleMissedCallInCallLogAsRead(
+ @NonNull Context context, @Nullable Uri callUri) {
+ if (callUri == null) {
+ LogUtil.e(
+ "CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead",
+ "call URI is null, unable to mark call as read");
+ } else {
+ markMissedCallsInCallLogAsRead(context, callUri);
+ }
+ }
+
/**
- * Removes the missed call notifications and marks calls as read. If a callUri is provided, only
- * that call is marked as read.
+ * If callUri is null then calls with a matching callUri are marked as read, otherwise all calls
+ * are marked as read.
*/
@WorkerThread
- public static void removeMissedCallNotifications(Context context, @Nullable Uri callUri) {
- // Call log is only accessible when unlocked. If that's the case, clear the list of
- // new missed calls from the call log.
- if (UserManagerCompat.isUserUnlocked(context) && PermissionsUtil.hasPhonePermissions(context)) {
- ContentValues values = new ContentValues();
- values.put(Calls.NEW, 0);
- values.put(Calls.IS_READ, 1);
- StringBuilder where = new StringBuilder();
- where.append(Calls.NEW);
- where.append(" = 1 AND ");
- where.append(Calls.TYPE);
- where.append(" = ?");
- try {
- context
- .getContentResolver()
- .update(
- callUri == null ? Calls.CONTENT_URI : callUri,
- values,
- where.toString(),
- new String[] {Integer.toString(Calls.MISSED_TYPE)});
- } catch (IllegalArgumentException e) {
- LogUtil.e(
- "CallLogNotificationsQueryHelper.removeMissedCallNotifications",
- "contacts provider update command failed",
- e);
- }
+ private static void markMissedCallsInCallLogAsRead(Context context, @Nullable Uri callUri) {
+ if (!UserManagerCompat.isUserUnlocked(context)) {
+ LogUtil.e("CallLogNotificationsQueryHelper.markMissedCallsInCallLogAsRead", "locked");
+ return;
+ }
+ if (!PermissionsUtil.hasPhonePermissions(context)) {
+ LogUtil.e("CallLogNotificationsQueryHelper.markMissedCallsInCallLogAsRead", "no permission");
+ return;
}
- GroupedNotificationUtil.removeNotification(
- context.getSystemService(NotificationManager.class),
- callUri != null ? callUri.toString() : null,
- R.id.notification_missed_call,
- MissedCallNotifier.NOTIFICATION_TAG);
+ ContentValues values = new ContentValues();
+ values.put(Calls.NEW, 0);
+ values.put(Calls.IS_READ, 1);
+ StringBuilder where = new StringBuilder();
+ where.append(Calls.NEW);
+ where.append(" = 1 AND ");
+ where.append(Calls.TYPE);
+ where.append(" = ?");
+ try {
+ context
+ .getContentResolver()
+ .update(
+ callUri == null ? Calls.CONTENT_URI : callUri,
+ values,
+ where.toString(),
+ new String[] {Integer.toString(Calls.MISSED_TYPE)});
+ } catch (IllegalArgumentException e) {
+ LogUtil.e(
+ "CallLogNotificationsQueryHelper.markMissedCallsInCallLogAsRead",
+ "contacts provider update command failed",
+ e);
+ }
}
/** Create a new instance of {@link NewCallsQuery}. */
@@ -281,7 +293,9 @@ public class CallLogNotificationsQueryHelper {
@TargetApi(VERSION_CODES.M)
public List<NewCall> query(int type) {
if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) {
- LogUtil.w(TAG, "No READ_CALL_LOG permission, returning null for calls lookup.");
+ LogUtil.w(
+ "CallLogNotificationsQueryHelper.DefaultNewCallsQuery.query",
+ "no READ_CALL_LOG permission, returning null for calls lookup.");
return null;
}
final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE);
@@ -302,7 +316,9 @@ public class CallLogNotificationsQueryHelper {
}
return newCalls;
} catch (RuntimeException e) {
- LogUtil.w(TAG, "Exception when querying Contacts Provider for calls lookup");
+ LogUtil.w(
+ "CallLogNotificationsQueryHelper.DefaultNewCallsQuery.query",
+ "exception when querying Contacts Provider for calls lookup");
return null;
}
}
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
index 7dfd2cb69..0490b9932 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
@@ -17,13 +17,20 @@
package com.android.dialer.app.calllog;
import android.app.IntentService;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccountHandle;
+import com.android.dialer.app.voicemail.LegacyVoicemailNotificationReceiver;
+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.telecom.TelecomUtil;
import com.android.dialer.util.PermissionsUtil;
@@ -43,28 +50,37 @@ import com.android.dialer.util.PermissionsUtil;
*/
public class CallLogNotificationsService extends IntentService {
- /** Action to mark all the new voicemails as old. */
- public static final String ACTION_MARK_NEW_VOICEMAILS_AS_OLD =
- "com.android.dialer.calllog.ACTION_MARK_NEW_VOICEMAILS_AS_OLD";
+ @VisibleForTesting
+ static final String ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD =
+ "com.android.dialer.calllog.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD";
- /** Action to mark all the new missed calls as old. */
- public static final String ACTION_MARK_NEW_MISSED_CALLS_AS_OLD =
- "com.android.dialer.calllog.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD";
+ private static final String ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD =
+ "com.android.dialer.calllog.ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD ";
- /** Action to update missed call notifications with a post call note. */
- public static final String ACTION_INCOMING_POST_CALL =
+ @VisibleForTesting
+ static final String ACTION_CANCEL_ALL_MISSED_CALLS =
+ "com.android.dialer.calllog.ACTION_CANCEL_ALL_MISSED_CALLS";
+
+ private static final String ACTION_CANCEL_SINGLE_MISSED_CALL =
+ "com.android.dialer.calllog.ACTION_CANCEL_SINGLE_MISSED_CALL";
+
+ private static final String ACTION_INCOMING_POST_CALL =
"com.android.dialer.calllog.INCOMING_POST_CALL";
/** Action to call back a missed call. */
public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION =
"com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION";
+ /** Action mark legacy voicemail as dismissed. */
+ public static final String ACTION_LEGACY_VOICEMAIL_DISMISSED =
+ "com.android.dialer.calllog.ACTION_LEGACY_VOICEMAIL_DISMISSED";
+
/**
* Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent a post call note.
*
* <p>It must be a {@link String}
*/
- public static final String EXTRA_POST_CALL_NOTE = "POST_CALL_NOTE";
+ private static final String EXTRA_POST_CALL_NOTE = "POST_CALL_NOTE";
/**
* Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent the phone number the
@@ -72,10 +88,11 @@ public class CallLogNotificationsService extends IntentService {
*
* <p>It must be a {@link String}
*/
- public static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER";
+ private static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER";
+
+ private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
public static final int UNKNOWN_MISSED_CALL_COUNT = -1;
- private VoicemailQueryHandler mVoicemailQueryHandler;
public CallLogNotificationsService() {
super("CallLogNotificationsService");
@@ -89,52 +106,107 @@ public class CallLogNotificationsService extends IntentService {
context.startService(serviceIntent);
}
- public static void markNewVoicemailsAsOld(Context context, @Nullable Uri voicemailUri) {
+ public static void markAllNewVoicemailsAsOld(Context context) {
+ LogUtil.enterBlock("CallLogNotificationsService.markAllNewVoicemailsAsOld");
Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
- serviceIntent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
- serviceIntent.setData(voicemailUri);
+ serviceIntent.setAction(CallLogNotificationsService.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD);
context.startService(serviceIntent);
}
- public static void markNewMissedCallsAsOld(Context context, @Nullable Uri callUri) {
+ public static void markSingleNewVoicemailAsOld(Context context, @Nullable Uri voicemailUri) {
+ LogUtil.enterBlock("CallLogNotificationsService.markSingleNewVoicemailAsOld");
Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
- serviceIntent.setAction(ACTION_MARK_NEW_MISSED_CALLS_AS_OLD);
- serviceIntent.setData(callUri);
+ 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)
+ .dialerExecutorFactory()
+ .createNonUiTaskBuilder(new CancelAllMissedCallsWorker())
+ .build()
+ .executeSerial(context);
+ }
+
+ public static PendingIntent createMarkAllNewVoicemailsAsOldIntent(@NonNull Context context) {
+ Intent intent = new Intent(context, CallLogNotificationsService.class);
+ intent.setAction(CallLogNotificationsService.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
+ public static PendingIntent createMarkSingleNewVoicemailAsOldIntent(
+ @NonNull Context context, @Nullable Uri voicemailUri) {
+ Intent intent = new Intent(context, CallLogNotificationsService.class);
+ intent.setAction(CallLogNotificationsService.ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD);
+ intent.setData(voicemailUri);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
+ public static PendingIntent createCancelAllMissedCallsPendingIntent(@NonNull Context context) {
+ Intent intent = new Intent(context, CallLogNotificationsService.class);
+ intent.setAction(ACTION_CANCEL_ALL_MISSED_CALLS);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
+ public static PendingIntent createCancelSingleMissedCallPendingIntent(
+ @NonNull Context context, @Nullable Uri callUri) {
+ Intent intent = new Intent(context, CallLogNotificationsService.class);
+ intent.setAction(ACTION_CANCEL_SINGLE_MISSED_CALL);
+ intent.setData(callUri);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
+ public static PendingIntent createLegacyVoicemailDismissedPendingIntent(
+ @NonNull Context context, PhoneAccountHandle phoneAccountHandle) {
+ Intent intent = new Intent(context, CallLogNotificationsService.class);
+ intent.setAction(ACTION_LEGACY_VOICEMAIL_DISMISSED);
+ intent.putExtra(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
- LogUtil.d("CallLogNotificationsService.onHandleIntent", "could not handle null intent");
+ LogUtil.e("CallLogNotificationsService.onHandleIntent", "could not handle null intent");
return;
}
- if (!PermissionsUtil.hasPermission(this, android.Manifest.permission.READ_CALL_LOG)) {
+ if (!PermissionsUtil.hasPermission(this, android.Manifest.permission.READ_CALL_LOG)
+ || !PermissionsUtil.hasPermission(this, android.Manifest.permission.WRITE_CALL_LOG)) {
+ LogUtil.e("CallLogNotificationsService.onHandleIntent", "no READ_CALL_LOG permission");
return;
}
String action = intent.getAction();
+ LogUtil.i("CallLogNotificationsService.onHandleIntent", "action: " + action);
switch (action) {
- case ACTION_MARK_NEW_VOICEMAILS_AS_OLD:
- // VoicemailQueryHandler cannot be created on the IntentService worker thread. The completed
- // callback might happen when the thread is dead.
- Handler handler = new Handler(Looper.getMainLooper());
- handler.post(
- () -> {
- if (mVoicemailQueryHandler == null) {
- mVoicemailQueryHandler = new VoicemailQueryHandler(this, getContentResolver());
- }
- mVoicemailQueryHandler.markNewVoicemailsAsOld(intent.getData());
- });
+ case ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD:
+ VoicemailQueryHandler.markAllNewVoicemailsAsRead(this);
+ VisualVoicemailNotifier.cancelAllVoicemailNotifications(this);
+ break;
+ case ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD:
+ Uri voicemailUri = intent.getData();
+ VoicemailQueryHandler.markSingleNewVoicemailAsRead(this, voicemailUri);
+ VisualVoicemailNotifier.cancelSingleVoicemailNotification(this, voicemailUri);
+ break;
+ case ACTION_LEGACY_VOICEMAIL_DISMISSED:
+ LegacyVoicemailNotificationReceiver.setDismissed(
+ this, intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE), true);
break;
case ACTION_INCOMING_POST_CALL:
String note = intent.getStringExtra(EXTRA_POST_CALL_NOTE);
String phoneNumber = intent.getStringExtra(EXTRA_POST_CALL_NUMBER);
MissedCallNotifier.getIstance(this).insertPostCallNotification(phoneNumber, note);
break;
- case ACTION_MARK_NEW_MISSED_CALLS_AS_OLD:
- CallLogNotificationsQueryHelper.removeMissedCallNotifications(this, intent.getData());
+ case ACTION_CANCEL_ALL_MISSED_CALLS:
+ cancelAllMissedCalls(this);
+ break;
+ case ACTION_CANCEL_SINGLE_MISSED_CALL:
+ Uri callUri = intent.getData();
+ CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(this, callUri);
+ MissedCallNotifier.cancelSingleMissedCallNotification(this, callUri);
TelecomUtil.cancelMissedCallsNotification(this);
break;
case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION:
@@ -145,8 +217,30 @@ public class CallLogNotificationsService extends IntentService {
intent.getData());
break;
default:
- LogUtil.d("CallLogNotificationsService.onHandleIntent", "could not handle: " + intent);
+ LogUtil.e("CallLogNotificationsService.onHandleIntent", "no handler for action: " + action);
break;
}
}
+
+ @WorkerThread
+ private static void cancelAllMissedCallsBackground(Context context) {
+ LogUtil.enterBlock("CallLogNotificationsService.cancelAllMissedCallsBackground");
+ Assert.isWorkerThread();
+ CallLogNotificationsQueryHelper.markAllMissedCallsInCallLogAsRead(context);
+ MissedCallNotifier.cancelAllMissedCallNotifications(context);
+ TelecomUtil.cancelMissedCallsNotification(context);
+ }
+
+ /** Worker that cancels all missed call notifications and updates call log entries. */
+ private static class CancelAllMissedCallsWorker implements Worker<Context, Void> {
+
+ @Nullable
+ @Override
+ public Void doInBackground(@Nullable Context context) throws Throwable {
+ if (context != null) {
+ cancelAllMissedCallsBackground(context);
+ }
+ return null;
+ }
+ }
}
diff --git a/java/com/android/dialer/app/calllog/CallLogReceiver.java b/java/com/android/dialer/app/calllog/CallLogReceiver.java
index 172d00100..ce3132d12 100644
--- a/java/com/android/dialer/app/calllog/CallLogReceiver.java
+++ b/java/com/android/dialer/app/calllog/CallLogReceiver.java
@@ -39,10 +39,10 @@ public class CallLogReceiver extends BroadcastReceiver {
if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) {
checkVoicemailStatus(context);
PendingResult pendingResult = goAsync();
- DefaultVoicemailNotifier.updateVoicemailNotifications(context, pendingResult::finish);
+ VisualVoicemailUpdateTask.scheduleTask(context, pendingResult::finish);
} else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
PendingResult pendingResult = goAsync();
- DefaultVoicemailNotifier.updateVoicemailNotifications(context, pendingResult::finish);
+ VisualVoicemailUpdateTask.scheduleTask(context, pendingResult::finish);
} else {
LogUtil.w("CallLogReceiver.onReceive", "could not handle: " + intent);
}
diff --git a/java/com/android/dialer/app/calllog/ClearCallLogDialog.java b/java/com/android/dialer/app/calllog/ClearCallLogDialog.java
index a01b89527..b16eb1beb 100644
--- a/java/com/android/dialer/app/calllog/ClearCallLogDialog.java
+++ b/java/com/android/dialer/app/calllog/ClearCallLogDialog.java
@@ -22,20 +22,27 @@ import android.app.Dialog;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.ProgressDialog;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.CallLog.Calls;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
import com.android.dialer.app.R;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.concurrent.DialerExecutor;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.enrichedcall.EnrichedCallComponent;
import com.android.dialer.phonenumbercache.CachedNumberLookupService;
import com.android.dialer.phonenumbercache.PhoneNumberCache;
/** Dialog that clears the call log after confirming with the user */
public class ClearCallLogDialog extends DialogFragment {
+ private DialerExecutor<Void> clearCallLogTask;
+ private ProgressDialog progressDialog;
+
/** Preferred way to show this dialog */
public static void show(FragmentManager fragmentManager) {
ClearCallLogDialog dialog = new ClearCallLogDialog();
@@ -43,49 +50,35 @@ public class ClearCallLogDialog extends DialogFragment {
}
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ clearCallLogTask =
+ DialerExecutorComponent.get(getContext())
+ .dialerExecutorFactory()
+ .createUiTaskBuilder(
+ getFragmentManager(),
+ "clearCallLogTask",
+ new ClearCallLogWorker(getActivity().getApplicationContext()))
+ .onSuccess(this::onSuccess)
+ .build();
+ }
+
+ @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- final ContentResolver resolver = getActivity().getContentResolver();
- final Context context = getActivity().getApplicationContext();
- final OnClickListener okListener =
- new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final ProgressDialog progressDialog =
- ProgressDialog.show(
- getActivity(), getString(R.string.clearCallLogProgress_title), "", true, false);
- progressDialog.setOwnerActivity(getActivity());
- CallLogNotificationsService.markNewMissedCallsAsOld(getContext(), null);
- final AsyncTask<Void, Void, Void> task =
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- resolver.delete(Calls.CONTENT_URI, null, null);
- CachedNumberLookupService cachedNumberLookupService =
- PhoneNumberCache.get(context).getCachedNumberLookupService();
- if (cachedNumberLookupService != null) {
- cachedNumberLookupService.clearAllCacheEntries(context);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- final Activity activity = progressDialog.getOwnerActivity();
-
- if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
- return;
- }
-
- if (progressDialog != null && progressDialog.isShowing()) {
- progressDialog.dismiss();
- }
- }
- };
- // TODO: Once we have the API, we should configure this ProgressDialog
- // to only show up after a certain time (e.g. 150ms)
- progressDialog.show();
- task.execute();
- }
+ OnClickListener okListener =
+ (dialog, which) -> {
+ progressDialog =
+ ProgressDialog.show(
+ getActivity(), getString(R.string.clearCallLogProgress_title), "", true, false);
+ progressDialog.setOwnerActivity(getActivity());
+ CallLogNotificationsService.cancelAllMissedCalls(getContext());
+
+ // TODO: Once we have the API, we should configure this ProgressDialog
+ // to only show up after a certain time (e.g. 150ms)
+ progressDialog.show();
+
+ clearCallLogTask.executeSerial(null);
};
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.clearCallLogConfirmation_title)
@@ -96,4 +89,49 @@ public class ClearCallLogDialog extends DialogFragment {
.setCancelable(true)
.create();
}
+
+ private static class ClearCallLogWorker implements Worker<Void, Void> {
+ private final Context appContext;
+
+ private ClearCallLogWorker(Context appContext) {
+ this.appContext = appContext;
+ }
+
+ @Nullable
+ @Override
+ public Void doInBackground(@Nullable Void unused) throws Throwable {
+ appContext.getContentResolver().delete(Calls.CONTENT_URI, null, null);
+ CachedNumberLookupService cachedNumberLookupService =
+ PhoneNumberCache.get(appContext).getCachedNumberLookupService();
+ if (cachedNumberLookupService != null) {
+ cachedNumberLookupService.clearAllCacheEntries(appContext);
+ }
+ return null;
+ }
+ }
+
+ private void onSuccess(Void unused) {
+ Assert.isNotNull(progressDialog);
+ Activity activity = progressDialog.getOwnerActivity();
+
+ if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
+ return;
+ }
+
+ maybeShowEnrichedCallSnackbar(activity);
+
+ if (progressDialog != null && progressDialog.isShowing()) {
+ progressDialog.dismiss();
+ }
+ }
+
+ private void maybeShowEnrichedCallSnackbar(Activity activity) {
+ if (EnrichedCallComponent.get(activity).getEnrichedCallManager().hasStoredData()) {
+ Snackbar.make(
+ activity.findViewById(R.id.calllog_frame),
+ getString(R.string.multiple_ec_data_deleted),
+ 5_000)
+ .show();
+ }
+ }
}
diff --git a/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java b/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java
deleted file mode 100644
index 58fe6fa2c..000000000
--- a/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Copyright (C) 2011 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.app.calllog;
-
-import android.annotation.TargetApi;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.support.v4.os.BuildCompat;
-import android.support.v4.util.Pair;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import com.android.contacts.common.compat.TelephonyManagerCompat;
-import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.dialer.app.DialtactsActivity;
-import com.android.dialer.app.R;
-import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall;
-import com.android.dialer.app.contactinfo.ContactPhotoLoader;
-import com.android.dialer.app.list.DialtactsPagerAdapter;
-import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
-import com.android.dialer.blocking.FilteredNumbersUtil;
-import com.android.dialer.calllogutils.PhoneAccountUtils;
-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.DialerExecutors;
-import com.android.dialer.logging.DialerImpression;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.notification.NotificationChannelManager;
-import com.android.dialer.notification.NotificationChannelManager.Channel;
-import com.android.dialer.phonenumbercache.ContactInfo;
-import com.android.dialer.telecom.TelecomUtil;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/** Shows a voicemail notification in the status bar. */
-public class DefaultVoicemailNotifier implements Worker<Void, Void> {
-
- public static final String TAG = "VoicemailNotifier";
-
- /** The tag used to identify notifications from this class. */
- static final String VISUAL_VOICEMAIL_NOTIFICATION_TAG = "DefaultVoicemailNotifier";
- /** The identifier of the notification of new voicemails. */
- private static final int VISUAL_VOICEMAIL_NOTIFICATION_ID = R.id.notification_visual_voicemail;
-
- private static final int LEGACY_VOICEMAIL_NOTIFICATION_ID = R.id.notification_legacy_voicemail;
- private static final String LEGACY_VOICEMAIL_NOTIFICATION_TAG = "legacy_voicemail";
-
- private final Context context;
- private final CallLogNotificationsQueryHelper queryHelper;
- private final FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler;
-
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- DefaultVoicemailNotifier(
- Context context,
- CallLogNotificationsQueryHelper queryHelper,
- FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler) {
- this.context = context;
- this.queryHelper = queryHelper;
- this.filteredNumberAsyncQueryHandler = filteredNumberAsyncQueryHandler;
- }
-
- public DefaultVoicemailNotifier(Context context) {
- this(
- context,
- CallLogNotificationsQueryHelper.getInstance(context),
- new FilteredNumberAsyncQueryHandler(context));
- }
-
- @Nullable
- @Override
- public Void doInBackground(@Nullable Void input) throws Throwable {
- updateNotification();
- return null;
- }
-
- /**
- * Updates the notification and notifies of the call with the given URI.
- *
- * <p>Clears the notification if there are no new voicemails, and notifies if the given URI
- * corresponds to a new voicemail.
- *
- * <p>It is not safe to call this method from the main thread.
- */
- @VisibleForTesting
- @WorkerThread
- void updateNotification() {
- Assert.isWorkerThread();
- // Lookup the list of new voicemails to include in the notification.
- final List<NewCall> newCalls = queryHelper.getNewVoicemails();
-
- if (newCalls == null) {
- // Query failed, just return.
- return;
- }
-
- Resources resources = context.getResources();
-
- // This represents a list of names to include in the notification.
- String callers = null;
-
- // Maps each number into a name: if a number is in the map, it has already left a more
- // recent voicemail.
- final Map<String, ContactInfo> contactInfos = new ArrayMap<>();
-
- // Iterate over the new voicemails to determine all the information above.
- Iterator<NewCall> itr = newCalls.iterator();
- while (itr.hasNext()) {
- NewCall newCall = itr.next();
-
- // Skip notifying for numbers which are blocked.
- if (!FilteredNumbersUtil.hasRecentEmergencyCall(context)
- && filteredNumberAsyncQueryHandler.getBlockedIdSynchronous(
- newCall.number, newCall.countryIso)
- != null) {
- itr.remove();
-
- if (newCall.voicemailUri != null) {
- // Delete the voicemail.
- CallLogAsyncTaskUtil.deleteVoicemailSynchronous(context, newCall.voicemailUri);
- }
- continue;
- }
-
- // Check if we already know the name associated with this number.
- ContactInfo contactInfo = contactInfos.get(newCall.number);
- if (contactInfo == null) {
- contactInfo =
- queryHelper.getContactInfo(
- newCall.number, newCall.numberPresentation, newCall.countryIso);
- contactInfos.put(newCall.number, contactInfo);
- // This is a new caller. Add it to the back of the list of callers.
- if (TextUtils.isEmpty(callers)) {
- callers = contactInfo.name;
- } else {
- callers =
- resources.getString(
- R.string.notification_voicemail_callers_list, callers, contactInfo.name);
- }
- }
- }
-
- if (newCalls.isEmpty()) {
- // No voicemails to notify about
- return;
- }
-
- Notification.Builder groupSummary =
- createNotificationBuilder()
- .setContentTitle(
- resources.getQuantityString(
- R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()))
- .setContentText(callers)
- .setDeleteIntent(createMarkNewVoicemailsAsOldIntent(null))
- .setGroupSummary(true)
- .setContentIntent(newVoicemailIntent(null));
-
- if (BuildCompat.isAtLeastO()) {
- groupSummary.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
- }
-
- NotificationChannelManager.applyChannel(
- groupSummary,
- context,
- Channel.VOICEMAIL,
- PhoneAccountHandles.getAccount(context, newCalls.get(0)));
-
- LogUtil.i(TAG, "Creating visual voicemail notification");
- getNotificationManager()
- .notify(
- VISUAL_VOICEMAIL_NOTIFICATION_TAG,
- VISUAL_VOICEMAIL_NOTIFICATION_ID,
- groupSummary.build());
-
- for (NewCall voicemail : newCalls) {
- getNotificationManager()
- .notify(
- voicemail.callsUri.toString(),
- VISUAL_VOICEMAIL_NOTIFICATION_ID,
- createNotificationForVoicemail(voicemail, contactInfos));
- }
- }
-
- /**
- * Replicates how packages/services/Telephony/NotificationMgr.java handles legacy voicemail
- * notification. The notification will not be stackable because no information is available for
- * individual voicemails.
- */
- @TargetApi(VERSION_CODES.O)
- public void notifyLegacyVoicemail(
- @NonNull PhoneAccountHandle phoneAccountHandle,
- int count,
- String voicemailNumber,
- PendingIntent callVoicemailIntent,
- PendingIntent voicemailSettingIntent) {
- Assert.isNotNull(phoneAccountHandle);
- Assert.checkArgument(BuildCompat.isAtLeastO());
- TelephonyManager telephonyManager =
- context
- .getSystemService(TelephonyManager.class)
- .createForPhoneAccountHandle(phoneAccountHandle);
- Assert.isNotNull(telephonyManager);
- LogUtil.i(TAG, "Creating legacy voicemail notification");
-
- PersistableBundle carrierConfig = telephonyManager.getCarrierConfig();
-
- String notificationTitle =
- context
- .getResources()
- .getQuantityString(R.plurals.notification_voicemail_title, count, count);
-
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- PhoneAccount phoneAccount = telecomManager.getPhoneAccount(phoneAccountHandle);
-
- String notificationText;
- PendingIntent pendingIntent;
-
- if (voicemailSettingIntent != null) {
- // If the voicemail number if unknown, instead of calling voicemail, take the user
- // to the voicemail settings.
- notificationText = context.getString(R.string.notification_voicemail_no_vm_number);
- pendingIntent = voicemailSettingIntent;
- } else {
- if (PhoneAccountUtils.getSubscriptionPhoneAccounts(context).size() > 1) {
- notificationText = phoneAccount.getShortDescription().toString();
- } else {
- notificationText =
- String.format(
- context.getString(R.string.notification_voicemail_text_format),
- PhoneNumberUtils.formatNumber(voicemailNumber));
- }
- pendingIntent = callVoicemailIntent;
- }
- Notification.Builder builder = new Notification.Builder(context);
- builder
- .setSmallIcon(android.R.drawable.stat_notify_voicemail)
- .setColor(context.getColor(R.color.dialer_theme_color))
- .setWhen(System.currentTimeMillis())
- .setContentTitle(notificationTitle)
- .setContentText(notificationText)
- .setContentIntent(pendingIntent)
- .setSound(telephonyManager.getVoicemailRingtoneUri(phoneAccountHandle))
- .setOngoing(
- carrierConfig.getBoolean(
- CarrierConfigManager.KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL));
-
- if (telephonyManager.isVoicemailVibrationEnabled(phoneAccountHandle)) {
- builder.setDefaults(Notification.DEFAULT_VIBRATE);
- }
-
- NotificationChannelManager.applyChannel(
- builder, context, Channel.VOICEMAIL, phoneAccountHandle);
- Notification notification = builder.build();
- getNotificationManager()
- .notify(LEGACY_VOICEMAIL_NOTIFICATION_TAG, LEGACY_VOICEMAIL_NOTIFICATION_ID, notification);
- }
-
- public void cancelLegacyNotification() {
- LogUtil.i(TAG, "Clearing legacy voicemail notification");
- getNotificationManager()
- .cancel(LEGACY_VOICEMAIL_NOTIFICATION_TAG, LEGACY_VOICEMAIL_NOTIFICATION_ID);
- }
-
- /**
- * Determines which ringtone Uri and Notification defaults to use when updating the notification
- * for the given call.
- */
- private Pair<Uri, Integer> getNotificationInfo(@Nullable NewCall callToNotify) {
- LogUtil.v(TAG, "getNotificationInfo");
- if (callToNotify == null) {
- LogUtil.i(TAG, "callToNotify == null");
- return new Pair<>(null, 0);
- }
- PhoneAccountHandle accountHandle = PhoneAccountHandles.getAccount(context, callToNotify);
- if (accountHandle == null) {
- LogUtil.i(TAG, "No default phone account found, using default notification ringtone");
- return new Pair<>(null, Notification.DEFAULT_ALL);
- }
- return new Pair<>(
- TelephonyManagerCompat.getVoicemailRingtoneUri(getTelephonyManager(), accountHandle),
- getNotificationDefaults(accountHandle));
- }
-
- private int getNotificationDefaults(PhoneAccountHandle accountHandle) {
- if (VERSION.SDK_INT >= VERSION_CODES.N) {
- return TelephonyManagerCompat.isVoicemailVibrationEnabled(
- getTelephonyManager(), accountHandle)
- ? Notification.DEFAULT_VIBRATE
- : 0;
- }
- return Notification.DEFAULT_ALL;
- }
-
- /** Creates a pending intent that marks all new voicemails as old. */
- private PendingIntent createMarkNewVoicemailsAsOldIntent(@Nullable Uri voicemailUri) {
- Intent intent = new Intent(context, CallLogNotificationsService.class);
- intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
- intent.setData(voicemailUri);
- return PendingIntent.getService(context, 0, intent, 0);
- }
-
- private NotificationManager getNotificationManager() {
- return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- }
-
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
- private Notification createNotificationForVoicemail(
- @NonNull NewCall voicemail, @NonNull Map<String, ContactInfo> contactInfos) {
- Pair<Uri, Integer> notificationInfo = getNotificationInfo(voicemail);
- ContactInfo contactInfo = contactInfos.get(voicemail.number);
-
- Notification.Builder notificationBuilder =
- createNotificationBuilder()
- .setContentTitle(
- context
- .getResources()
- .getQuantityString(R.plurals.notification_voicemail_title, 1, 1))
- .setContentText(
- ContactDisplayUtils.getTtsSpannedPhoneNumber(
- context.getResources(),
- R.string.notification_new_voicemail_ticker,
- contactInfo.name))
- .setWhen(voicemail.dateMs)
- .setSound(notificationInfo.first)
- .setDefaults(notificationInfo.second);
-
- if (voicemail.voicemailUri != null) {
- notificationBuilder.setDeleteIntent(
- createMarkNewVoicemailsAsOldIntent(voicemail.voicemailUri));
- }
-
- NotificationChannelManager.applyChannel(
- notificationBuilder,
- context,
- Channel.VOICEMAIL,
- PhoneAccountHandles.getAccount(context, voicemail));
-
- ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo);
- Bitmap photoIcon = loader.loadPhotoIcon();
- if (photoIcon != null) {
- notificationBuilder.setLargeIcon(photoIcon);
- }
- if (!TextUtils.isEmpty(voicemail.transcription)) {
- Logger.get(context)
- .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION);
- notificationBuilder.setStyle(
- new Notification.BigTextStyle().bigText(voicemail.transcription));
- }
- notificationBuilder.setContentIntent(newVoicemailIntent(voicemail));
- Logger.get(context).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED);
- return notificationBuilder.build();
- }
-
- private Notification.Builder createNotificationBuilder() {
- return new Notification.Builder(context)
- .setSmallIcon(android.R.drawable.stat_notify_voicemail)
- .setColor(context.getColor(R.color.dialer_theme_color))
- .setGroup(VISUAL_VOICEMAIL_NOTIFICATION_TAG)
- .setOnlyAlertOnce(true)
- .setAutoCancel(true);
- }
-
- private PendingIntent newVoicemailIntent(@Nullable NewCall voicemail) {
- Intent intent =
- DialtactsActivity.getShowTabIntent(context, DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
- // TODO (b/35486204): scroll to this voicemail
- if (voicemail != null) {
- intent.setData(voicemail.voicemailUri);
- }
- intent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true);
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- /**
- * Updates the voicemail notifications displayed.
- *
- * @param runnable Called when the async update task completes no matter if it succeeds or fails.
- * May be null.
- */
- static void updateVoicemailNotifications(Context context, Runnable runnable) {
- if (!TelecomUtil.isDefaultDialer(context)) {
- LogUtil.i(
- "DefaultVoicemailNotifier.updateVoicemailNotifications",
- "not default dialer, not scheduling update to voicemail notifications");
- return;
- }
-
- DialerExecutors.createNonUiTaskBuilder(new DefaultVoicemailNotifier(context))
- .onSuccess(
- output -> {
- LogUtil.i(
- "DefaultVoicemailNotifier.updateVoicemailNotifications",
- "update voicemail notifications successful");
- if (runnable != null) {
- runnable.run();
- }
- })
- .onFailure(
- throwable -> {
- LogUtil.i(
- "DefaultVoicemailNotifier.updateVoicemailNotifications",
- "update voicemail notifications failed");
- if (runnable != null) {
- runnable.run();
- }
- })
- .build()
- .executeParallel(null);
- }
-}
diff --git a/java/com/android/dialer/app/calllog/DialerQuickContactBadge.java b/java/com/android/dialer/app/calllog/DialerQuickContactBadge.java
new file mode 100644
index 000000000..a3aac41fa
--- /dev/null
+++ b/java/com/android/dialer/app/calllog/DialerQuickContactBadge.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.app.calllog;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.QuickContactBadge;
+import com.android.dialer.app.calllog.CallLogAdapter.OnActionModeStateChangedListener;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+
+/** Allows us to click the contact badge for non multi select mode. */
+class DialerQuickContactBadge extends QuickContactBadge {
+
+ private View.OnClickListener mExtraOnClickListener;
+ private OnActionModeStateChangedListener onActionModeStateChangeListener;
+
+ public DialerQuickContactBadge(Context context) {
+ super(context);
+ }
+
+ public DialerQuickContactBadge(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DialerQuickContactBadge(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mExtraOnClickListener != null
+ && onActionModeStateChangeListener.isActionModeStateEnabled()) {
+ Logger.get(v.getContext())
+ .logImpression(DialerImpression.Type.MULTISELECT_SINGLE_PRESS_TAP_VIA_CONTACT_BADGE);
+ mExtraOnClickListener.onClick(v);
+ } else {
+ super.onClick(v);
+ }
+ }
+
+ public void setMulitSelectListeners(
+ View.OnClickListener extraOnClickListener,
+ OnActionModeStateChangedListener actionModeStateChangeListener) {
+ mExtraOnClickListener = extraOnClickListener;
+ onActionModeStateChangeListener = actionModeStateChangeListener;
+ }
+}
diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java
index a94c6781e..55fdbbace 100644
--- a/java/com/android/dialer/app/calllog/IntentProvider.java
+++ b/java/com/android/dialer/app/calllog/IntentProvider.java
@@ -24,11 +24,11 @@ import android.provider.ContactsContract;
import android.telecom.PhoneAccountHandle;
import com.android.contacts.common.model.Contact;
import com.android.contacts.common.model.ContactLoader;
-import com.android.dialer.callcomposer.CallComposerContact;
import com.android.dialer.calldetails.CallDetailsActivity;
import com.android.dialer.calldetails.CallDetailsEntries;
import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.dialercontact.DialerContact;
import com.android.dialer.lightbringer.LightbringerComponent;
import com.android.dialer.util.CallUtil;
import com.android.dialer.util.IntentUtil;
@@ -112,11 +112,12 @@ public abstract class IntentProvider {
* @return The call details intent provider.
*/
public static IntentProvider getCallDetailIntentProvider(
- CallDetailsEntries callDetailsEntries, CallComposerContact contact) {
+ CallDetailsEntries callDetailsEntries, DialerContact contact, boolean canReportCallerId) {
return new IntentProvider() {
@Override
public Intent getIntent(Context context) {
- return CallDetailsActivity.newInstance(context, callDetailsEntries, contact);
+ return CallDetailsActivity.newInstance(
+ context, callDetailsEntries, contact, canReportCallerId);
}
};
}
diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
new file mode 100644
index 000000000..584f07fe3
--- /dev/null
+++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.app.calllog;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.dialer.app.R;
+import com.android.dialer.calllogutils.PhoneAccountUtils;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.notification.DialerNotificationManager;
+import com.android.dialer.notification.NotificationChannelManager;
+
+/** Shows a notification in the status bar for legacy vociemail. */
+@TargetApi(VERSION_CODES.O)
+public final class LegacyVoicemailNotifier {
+ private static final String NOTIFICATION_TAG = "LegacyVoicemail";
+ private static final int NOTIFICATION_ID = 1;
+
+ /**
+ * Replicates how packages/services/Telephony/NotificationMgr.java handles legacy voicemail
+ * notification. The notification will not be stackable because no information is available for
+ * individual voicemails.
+ */
+ public static void showNotification(
+ @NonNull Context context,
+ @NonNull PhoneAccountHandle handle,
+ int count,
+ String voicemailNumber,
+ PendingIntent callVoicemailIntent,
+ PendingIntent voicemailSettingsIntent,
+ boolean isRefresh) {
+ LogUtil.enterBlock("LegacyVoicemailNotifier.showNotification");
+ Assert.isNotNull(handle);
+ Assert.checkArgument(BuildCompat.isAtLeastO());
+
+ TelephonyManager pinnedTelephonyManager =
+ context.getSystemService(TelephonyManager.class).createForPhoneAccountHandle(handle);
+ if (pinnedTelephonyManager == null) {
+ LogUtil.e("LegacyVoicemailNotifier.showNotification", "invalid PhoneAccountHandle");
+ return;
+ }
+
+ Notification notification =
+ createNotification(
+ context,
+ pinnedTelephonyManager,
+ handle,
+ count,
+ voicemailNumber,
+ callVoicemailIntent,
+ voicemailSettingsIntent,
+ isRefresh);
+ DialerNotificationManager.notify(context, NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+ }
+
+ @NonNull
+ private static Notification createNotification(
+ @NonNull Context context,
+ @NonNull TelephonyManager pinnedTelephonyManager,
+ @NonNull PhoneAccountHandle handle,
+ int count,
+ String voicemailNumber,
+ PendingIntent callVoicemailIntent,
+ PendingIntent voicemailSettingsIntent,
+ boolean isRefresh) {
+ String notificationTitle =
+ context
+ .getResources()
+ .getQuantityString(R.plurals.notification_voicemail_title, count, count);
+ boolean isOngoing =
+ pinnedTelephonyManager
+ .getCarrierConfig()
+ .getBoolean(CarrierConfigManager.KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL);
+
+ String contentText;
+ PendingIntent contentIntent;
+ if (!TextUtils.isEmpty(voicemailNumber) && callVoicemailIntent != null) {
+ contentText = getNotificationText(context, handle, voicemailNumber);
+ contentIntent = callVoicemailIntent;
+ } else {
+ contentText = context.getString(R.string.notification_voicemail_no_vm_number);
+ contentIntent = voicemailSettingsIntent;
+ }
+
+ Notification.Builder builder =
+ new Notification.Builder(context)
+ .setSmallIcon(android.R.drawable.stat_notify_voicemail)
+ .setColor(context.getColor(R.color.dialer_theme_color))
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle(notificationTitle)
+ .setContentText(contentText)
+ .setContentIntent(contentIntent)
+ .setSound(pinnedTelephonyManager.getVoicemailRingtoneUri(handle))
+ .setOngoing(isOngoing)
+ .setOnlyAlertOnce(isRefresh)
+ .setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle))
+ .setDeleteIntent(
+ CallLogNotificationsService.createLegacyVoicemailDismissedPendingIntent(
+ context, handle));
+
+ if (pinnedTelephonyManager.isVoicemailVibrationEnabled(handle)) {
+ builder.setDefaults(Notification.DEFAULT_VIBRATE);
+ }
+
+ return builder.build();
+ }
+
+ @NonNull
+ private static String getNotificationText(
+ @NonNull Context context, PhoneAccountHandle handle, String voicemailNumber) {
+ if (PhoneAccountUtils.getSubscriptionPhoneAccounts(context).size() > 1) {
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ PhoneAccount phoneAccount = telecomManager.getPhoneAccount(handle);
+ return phoneAccount.getShortDescription().toString();
+ } else {
+ return String.format(
+ context.getString(R.string.notification_voicemail_text_format),
+ PhoneNumberUtils.formatNumber(voicemailNumber));
+ }
+ }
+
+ public static void cancelNotification(@NonNull Context context) {
+ LogUtil.enterBlock("LegacyVoicemailNotifier.cancelNotification");
+ Assert.checkArgument(BuildCompat.isAtLeastO());
+ DialerNotificationManager.cancel(context, NOTIFICATION_TAG, NOTIFICATION_ID);
+ }
+
+ private LegacyVoicemailNotifier() {}
+}
diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
index dd13298bc..b363b5ab6 100644
--- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java
+++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
@@ -17,7 +17,6 @@ package com.android.dialer.app.calllog;
import android.app.Notification;
import android.app.Notification.Builder;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -30,11 +29,13 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
+import android.support.v4.os.BuildCompat;
import android.support.v4.os.UserManagerCompat;
import android.support.v4.util.Pair;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
import com.android.dialer.app.DialtactsActivity;
@@ -46,23 +47,30 @@ import com.android.dialer.callintent.CallInitiationType;
import com.android.dialer.callintent.CallIntentBuilder;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutor.Worker;
-import com.android.dialer.notification.NotificationChannelManager;
-import com.android.dialer.notification.NotificationChannelManager.Channel;
+import com.android.dialer.notification.DialerNotificationManager;
+import com.android.dialer.notification.NotificationChannelId;
+import com.android.dialer.notification.NotificationManagerUtils;
import com.android.dialer.phonenumbercache.ContactInfo;
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.IntentUtil;
-import java.util.HashSet;
import java.util.List;
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> {
- /** The tag used to identify notifications from this class. */
- static final String NOTIFICATION_TAG = "MissedCallNotifier";
- /** The identifier of the notification of new missed calls. */
- private static final int NOTIFICATION_ID = R.id.notification_missed_call;
+ /** 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;
@@ -104,7 +112,8 @@ 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.removeMissedCallNotifications(context, null);
+ CallLogNotificationsQueryHelper.markAllMissedCallsInCallLogAsRead(context);
+ cancelAllMissedCallNotifications(context);
return;
}
@@ -146,7 +155,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
null,
System.currentTimeMillis());
- //TODO: look up caller ID that is not in contacts.
+ // TODO: look up caller ID that is not in contacts.
ContactInfo contactInfo =
callLogNotificationsQueryHelper.getContactInfo(
call.number, call.numberPresentation, call.countryIso);
@@ -181,52 +190,84 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
publicSummaryBuilder
.setContentTitle(context.getText(titleResId))
.setContentIntent(createCallLogPendingIntent())
- .setDeleteIntent(createClearMissedCallsPendingIntent(null));
+ .setDeleteIntent(
+ CallLogNotificationsService.createCancelAllMissedCallsPendingIntent(context));
// Create the notification summary suitable for display when sensitive information is showing.
groupSummary
.setContentTitle(context.getText(titleResId))
.setContentText(expandedText)
.setContentIntent(createCallLogPendingIntent())
- .setDeleteIntent(createClearMissedCallsPendingIntent(null))
+ .setDeleteIntent(
+ CallLogNotificationsService.createCancelAllMissedCallsPendingIntent(context))
.setGroupSummary(useCallList)
.setOnlyAlertOnce(useCallList)
.setPublicVersion(publicSummaryBuilder.build());
-
- NotificationChannelManager.applyChannel(groupSummary, context, Channel.MISSED_CALL, null);
+ if (BuildCompat.isAtLeastO()) {
+ groupSummary.setChannelId(NotificationChannelId.MISSED_CALL);
+ }
Notification notification = groupSummary.build();
configureLedOnNotification(notification);
LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification");
- getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+ DialerNotificationManager.notify(
+ context, GROUP_SUMMARY_NOTIFICATION_TAG, NOTIFICATION_ID, notification);
if (useCallList) {
// Do not repost active notifications to prevent erasing post call notes.
- NotificationManager manager = getNotificationMgr();
- Set<String> activeTags = new HashSet<>();
- for (StatusBarNotification activeNotification : manager.getActiveNotifications()) {
+ Set<String> activeTags = new ArraySet<>();
+ for (StatusBarNotification activeNotification :
+ DialerNotificationManager.getActiveNotifications(context)) {
activeTags.add(activeNotification.getTag());
}
for (NewCall call : newCalls) {
- String callTag = call.callsUri.toString();
+ String callTag = getNotificationTagForCall(call);
if (!activeTags.contains(callTag)) {
- manager.notify(callTag, NOTIFICATION_ID, getNotificationForCall(call, null));
+ DialerNotificationManager.notify(
+ context, callTag, NOTIFICATION_ID, getNotificationForCall(call, null));
}
}
}
}
+ 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;
+ }
+
public void insertPostCallNotification(@NonNull String number, @NonNull String note) {
List<NewCall> newCalls = callLogNotificationsQueryHelper.getNewMissedCalls();
if (newCalls != null && !newCalls.isEmpty()) {
for (NewCall call : newCalls) {
if (call.number.equals(number.replace("tel:", ""))) {
// Update the first notification that matches our post call note sender.
- getNotificationMgr()
- .notify(
- call.callsUri.toString(), NOTIFICATION_ID, getNotificationForCall(call, note));
+ DialerNotificationManager.notify(
+ context,
+ getNotificationTagForCall(call),
+ NOTIFICATION_ID,
+ getNotificationForCall(call, note));
break;
}
}
@@ -308,7 +349,7 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
private Notification.Builder createNotificationBuilder() {
return new Notification.Builder(context)
- .setGroup(NOTIFICATION_TAG)
+ .setGroup(GROUP_KEY)
.setSmallIcon(android.R.drawable.stat_notify_missed_call)
.setColor(context.getResources().getColor(R.color.dialer_theme_color, null))
.setAutoCancel(true)
@@ -321,10 +362,14 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
Builder builder =
createNotificationBuilder()
.setWhen(call.dateMs)
- .setDeleteIntent(createClearMissedCallsPendingIntent(call.callsUri))
+ .setDeleteIntent(
+ CallLogNotificationsService.createCancelSingleMissedCallPendingIntent(
+ context, call.callsUri))
.setContentIntent(createCallLogPendingIntent(call.callsUri));
+ if (BuildCompat.isAtLeastO()) {
+ builder.setChannelId(NotificationChannelId.MISSED_CALL);
+ }
- NotificationChannelManager.applyChannel(builder, context, Channel.MISSED_CALL, null);
return builder;
}
@@ -332,7 +377,8 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
@WorkerThread
public void callBackFromMissedCall(String number, Uri callUri) {
closeSystemDialogs(context);
- CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri);
+ CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(context, callUri);
+ cancelSingleMissedCallNotification(context, callUri);
DialerUtils.startActivityWithErrorToast(
context,
new CallIntentBuilder(number, CallInitiationType.Type.MISSED_CALL_NOTIFICATION)
@@ -343,7 +389,8 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
/** Trigger an intent to send an sms from a missed call number. */
public void sendSmsFromMissedCall(String number, Uri callUri) {
closeSystemDialogs(context);
- CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri);
+ CallLogNotificationsQueryHelper.markSingleMissedCallInCallLogAsRead(context, callUri);
+ cancelSingleMissedCallNotification(context, callUri);
DialerUtils.startActivityWithErrorToast(
context, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
@@ -371,14 +418,6 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
return PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
- /** Creates a pending intent that marks all new missed calls as old. */
- private PendingIntent createClearMissedCallsPendingIntent(@Nullable Uri callUri) {
- Intent intent = new Intent(context, CallLogNotificationsService.class);
- intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD);
- intent.setData(callUri);
- return PendingIntent.getService(context, 0, intent, 0);
- }
-
private PendingIntent createCallBackPendingIntent(String number, @NonNull Uri callUri) {
Intent intent = new Intent(context, CallLogNotificationsService.class);
intent.setAction(CallLogNotificationsService.ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION);
@@ -410,8 +449,4 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> {
private void closeSystemDialogs(Context context) {
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
-
- private NotificationManager getNotificationMgr() {
- return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- }
}
diff --git a/java/com/android/dialer/app/calllog/PhoneAccountHandles.java b/java/com/android/dialer/app/calllog/PhoneAccountHandles.java
deleted file mode 100644
index acffffb1d..000000000
--- a/java/com/android/dialer/app/calllog/PhoneAccountHandles.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 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.app.calllog;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.telecom.TelecomUtil;
-import java.util.List;
-
-/** Methods to help extract {@link PhoneAccount} information from database and Telecomm sources. */
-class PhoneAccountHandles {
-
- @Nullable
- public static PhoneAccountHandle getAccount(@NonNull Context context, @Nullable NewCall call) {
- PhoneAccountHandle handle;
- if (call == null || call.accountComponentName == null || call.accountId == null) {
- LogUtil.v(
- "PhoneAccountUtils.getAccount",
- "accountComponentName == null || callToNotify.accountId == null");
- handle = TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL);
- if (handle == null) {
- List<PhoneAccountHandle> callCapablePhoneAccounts =
- TelecomUtil.getCallCapablePhoneAccounts(context);
- if (!callCapablePhoneAccounts.isEmpty()) {
- return callCapablePhoneAccounts.get(0);
- }
- return null;
- }
- } else {
- handle =
- new PhoneAccountHandle(
- ComponentName.unflattenFromString(call.accountComponentName), call.accountId);
- }
- if (handle.getComponentName() != null) {
- LogUtil.v(
- "PhoneAccountUtils.getAccount",
- "PhoneAccountHandle.ComponentInfo:" + handle.getComponentName());
- } else {
- LogUtil.i("PhoneAccountUtils.getAccount", "PhoneAccountHandle.ComponentInfo: null");
- }
- return handle;
- }
-}
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
index 0c720775a..c1a00e58d 100644
--- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
+++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java
@@ -45,6 +45,13 @@ public class PhoneCallDetailsHelper {
/** The maximum number of icons will be shown to represent the call types in a group. */
private static final int MAX_CALL_TYPE_ICONS = 3;
+ // TODO(mdooley): remove when these api's become public
+ // Copied from android.provider.VoicemailContract
+ static final int TRANSCRIPTION_NOT_STARTED = 0;
+ static final int TRANSCRIPTION_IN_PROGRESS = 1;
+ static final int TRANSCRIPTION_FAILED = 2;
+ static final int TRANSCRIPTION_AVAILABLE = 3;
+
private final Context mContext;
private final Resources mResources;
private final CallLogCache mCallLogCache;
@@ -145,14 +152,37 @@ public class PhoneCallDetailsHelper {
if (isVoicemail) {
int relevantLinkTypes = Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS | Linkify.WEB_URLS;
views.voicemailTranscriptionView.setAutoLinkMask(relevantLinkTypes);
- views.voicemailTranscriptionView.setText(
- TextUtils.isEmpty(details.transcription) ? null : details.transcription);
+ boolean showTranscriptBranding = false;
+ if (!TextUtils.isEmpty(details.transcription)) {
+ views.voicemailTranscriptionView.setText(details.transcription);
+
+ // Set the branding text if the voicemail was transcribed by google
+ // TODO(mdooley): the transcription state is only set by the google transcription code,
+ // but a better solution would be to check the SOURCE_PACKAGE
+ showTranscriptBranding = details.transcriptionState == TRANSCRIPTION_AVAILABLE;
+ } else {
+ if (details.transcriptionState == TRANSCRIPTION_IN_PROGRESS) {
+ views.voicemailTranscriptionView.setText(
+ mResources.getString(R.string.voicemail_transcription_in_progress));
+ } else if (details.transcriptionState == TRANSCRIPTION_FAILED) {
+ views.voicemailTranscriptionView.setText(
+ mResources.getString(R.string.voicemail_transcription_failed));
+ }
+ }
+
+ if (showTranscriptBranding) {
+ views.voicemailTranscriptionBrandingView.setText(
+ mResources.getString(R.string.voicemail_transcription_branding_text));
+ } else {
+ views.voicemailTranscriptionBrandingView.setText("");
+ }
}
// Bold if not read
Typeface typeface = details.isRead ? Typeface.SANS_SERIF : Typeface.DEFAULT_BOLD;
views.nameView.setTypeface(typeface);
views.voicemailTranscriptionView.setTypeface(typeface);
+ views.voicemailTranscriptionBrandingView.setTypeface(typeface);
views.callLocationAndDate.setTypeface(typeface);
views.callLocationAndDate.setTextColor(
ContextCompat.getColor(
diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java
index e2e27a179..40c0894f0 100644
--- a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java
+++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java
@@ -29,7 +29,9 @@ public final class PhoneCallDetailsViews {
public final View callTypeView;
public final CallTypeIconsView callTypeIcons;
public final TextView callLocationAndDate;
+ public final View transcriptionView;
public final TextView voicemailTranscriptionView;
+ public final TextView voicemailTranscriptionBrandingView;
public final TextView callAccountLabel;
private PhoneCallDetailsViews(
@@ -37,13 +39,17 @@ public final class PhoneCallDetailsViews {
View callTypeView,
CallTypeIconsView callTypeIcons,
TextView callLocationAndDate,
+ View transcriptionView,
TextView voicemailTranscriptionView,
+ TextView voicemailTranscriptionBrandingView,
TextView callAccountLabel) {
this.nameView = nameView;
this.callTypeView = callTypeView;
this.callTypeIcons = callTypeIcons;
this.callLocationAndDate = callLocationAndDate;
+ this.transcriptionView = transcriptionView;
this.voicemailTranscriptionView = voicemailTranscriptionView;
+ this.voicemailTranscriptionBrandingView = voicemailTranscriptionBrandingView;
this.callAccountLabel = callAccountLabel;
}
@@ -60,7 +66,9 @@ public final class PhoneCallDetailsViews {
view.findViewById(R.id.call_type),
(CallTypeIconsView) view.findViewById(R.id.call_type_icons),
(TextView) view.findViewById(R.id.call_location_and_date),
+ view.findViewById(R.id.transcription),
(TextView) view.findViewById(R.id.voicemail_transcription),
+ (TextView) view.findViewById(R.id.voicemail_transcription_branding),
(TextView) view.findViewById(R.id.call_account_label));
}
@@ -70,6 +78,8 @@ public final class PhoneCallDetailsViews {
new View(context),
new CallTypeIconsView(context),
new TextView(context),
+ new View(context),
+ new TextView(context),
new TextView(context),
new TextView(context));
}
diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java b/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java
index 893d6bed9..8bfd48b05 100644
--- a/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java
@@ -16,12 +16,15 @@
package com.android.dialer.app.calllog;
+import android.app.KeyguardManager;
+import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.os.Bundle;
import android.provider.CallLog;
import android.provider.VoicemailContract;
+import android.support.annotation.VisibleForTesting;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,15 +33,22 @@ import com.android.dialer.app.list.ListsFragment;
import com.android.dialer.app.voicemail.VoicemailAudioManager;
import com.android.dialer.app.voicemail.VoicemailErrorManager;
import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
+import com.android.dialer.app.voicemail.error.VoicemailErrorMessageCreator;
+import com.android.dialer.app.voicemail.error.VoicemailStatus;
+import com.android.dialer.app.voicemail.error.VoicemailStatusWorker;
import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor;
+import com.android.dialer.common.concurrent.DialerExecutors;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.util.PermissionsUtil;
+import java.util.List;
public class VisualVoicemailCallLogFragment extends CallLogFragment {
private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver();
private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
+ private DialerExecutor<Context> mPreSyncVoicemailStatusCheckExecutor;
private VoicemailErrorManager mVoicemailErrorManager;
@@ -55,7 +65,6 @@ public class VisualVoicemailCallLogFragment extends CallLogFragment {
public void onActivityCreated(Bundle savedInstanceState) {
mVoicemailPlaybackPresenter =
VoicemailPlaybackPresenter.getInstance(getActivity(), savedInstanceState);
-
if (PermissionsUtil.hasReadVoicemailPermissions(getContext())
&& PermissionsUtil.hasAddVoicemailPermissions(getContext())) {
getActivity()
@@ -68,6 +77,15 @@ public class VisualVoicemailCallLogFragment extends CallLogFragment {
"read voicemail permission unavailable.");
}
super.onActivityCreated(savedInstanceState);
+
+ mPreSyncVoicemailStatusCheckExecutor =
+ DialerExecutors.createUiTaskBuilder(
+ getActivity().getFragmentManager(),
+ "fetchVoicemailStatus",
+ new VoicemailStatusWorker())
+ .onSuccess(this::onPreSyncVoicemailStatusChecked)
+ .build();
+
mVoicemailErrorManager =
new VoicemailErrorManager(getContext(), getAdapter().getAlertManager(), mModalAlertManager);
@@ -132,23 +150,52 @@ public class VisualVoicemailCallLogFragment extends CallLogFragment {
@Override
public void onVisible() {
- LogUtil.enterBlock("VisualVoicemailCallLogFragment.onPageSelected");
+ LogUtil.enterBlock("VisualVoicemailCallLogFragment.onVisible");
super.onVisible();
if (getActivity() != null) {
- Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
- intent.setPackage(getActivity().getPackageName());
- getActivity().sendBroadcast(intent);
+ mPreSyncVoicemailStatusCheckExecutor.executeParallel(getActivity());
Logger.get(getActivity()).logImpression(DialerImpression.Type.VVM_TAB_VIEWED);
getActivity().setVolumeControlStream(VoicemailAudioManager.PLAYBACK_STREAM);
}
}
+ private void onPreSyncVoicemailStatusChecked(List<VoicemailStatus> statuses) {
+ if (!shouldAutoSync(new VoicemailErrorMessageCreator(), statuses)) {
+ return;
+ }
+
+ Intent intent = new Intent(VoicemailContract.ACTION_SYNC_VOICEMAIL);
+ intent.setPackage(getActivity().getPackageName());
+ getActivity().sendBroadcast(intent);
+ }
+
+ @VisibleForTesting
+ static boolean shouldAutoSync(
+ VoicemailErrorMessageCreator errorMessageCreator, List<VoicemailStatus> statuses) {
+ for (VoicemailStatus status : statuses) {
+ if (!status.isActive()) {
+ continue;
+ }
+ if (errorMessageCreator.isSyncBlockingError(status)) {
+ LogUtil.i(
+ "VisualVoicemailCallLogFragment.shouldAutoSync", "auto-sync blocked due to " + status);
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
public void onNotVisible() {
- LogUtil.enterBlock("VisualVoicemailCallLogFragment.onPageUnselected");
+ LogUtil.enterBlock("VisualVoicemailCallLogFragment.onNotVisible");
super.onNotVisible();
if (getActivity() != null) {
getActivity().setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
+ // onNotVisible will be called in the lock screen when the call ends
+ if (!getActivity().getSystemService(KeyguardManager.class).inKeyguardRestrictedInputMode()) {
+ LogUtil.i("VisualVoicemailCallLogFragment.onNotVisible", "clearing all new voicemails");
+ CallLogNotificationsService.markAllNewVoicemailsAsOld(getActivity());
+ }
}
}
}
diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java
new file mode 100644
index 000000000..cbadfd317
--- /dev/null
+++ b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2017 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.app.calllog;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.os.BuildCompat;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.contacts.common.util.ContactDisplayUtils;
+import com.android.dialer.app.DialtactsActivity;
+import com.android.dialer.app.R;
+import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall;
+import com.android.dialer.app.contactinfo.ContactPhotoLoader;
+import com.android.dialer.app.list.DialtactsPagerAdapter;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.notification.DialerNotificationManager;
+import com.android.dialer.notification.NotificationChannelManager;
+import com.android.dialer.notification.NotificationManagerUtils;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.telecom.TelecomUtil;
+import java.util.List;
+import java.util.Map;
+
+/** Shows a notification in the status bar for visual voicemail. */
+final class VisualVoicemailNotifier {
+ /** Prefix used to generate a unique tag for each voicemail notification. */
+ private static final String NOTIFICATION_TAG_PREFIX = "VisualVoicemail_";
+ /** Common ID for all voicemail notifications. */
+ private static final int NOTIFICATION_ID = 1;
+ /** Tag for the group summary notification. */
+ private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_VisualVoicemail";
+ /**
+ * Key used to associate all voicemail notifications and the summary as belonging to a single
+ * group.
+ */
+ private static final String GROUP_KEY = "VisualVoicemailGroup";
+
+ public static void showNotifications(
+ @NonNull Context context,
+ @NonNull List<NewCall> newCalls,
+ @NonNull Map<String, ContactInfo> contactInfos,
+ @Nullable String callers) {
+ LogUtil.enterBlock("VisualVoicemailNotifier.showNotifications");
+ PendingIntent deleteIntent =
+ CallLogNotificationsService.createMarkAllNewVoicemailsAsOldIntent(context);
+ String contentTitle =
+ context
+ .getResources()
+ .getQuantityString(
+ R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size());
+ Notification.Builder groupSummary =
+ createNotificationBuilder(context)
+ .setContentTitle(contentTitle)
+ .setContentText(callers)
+ .setDeleteIntent(deleteIntent)
+ .setGroupSummary(true)
+ .setContentIntent(newVoicemailIntent(context, null));
+
+ if (BuildCompat.isAtLeastO()) {
+ groupSummary.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN);
+ PhoneAccountHandle handle = getAccountForCall(context, newCalls.get(0));
+ groupSummary.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle));
+ }
+
+ DialerNotificationManager.notify(
+ context, GROUP_SUMMARY_NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build());
+
+ for (NewCall voicemail : newCalls) {
+ DialerNotificationManager.notify(
+ context,
+ getNotificationTagForVoicemail(voicemail),
+ NOTIFICATION_ID,
+ createNotificationForVoicemail(context, voicemail, contactInfos));
+ }
+ }
+
+ public static void cancelAllVoicemailNotifications(@NonNull Context context) {
+ LogUtil.enterBlock("VisualVoicemailNotifier.cancelAllVoicemailNotifications");
+ NotificationManagerUtils.cancelAllInGroup(context, GROUP_KEY);
+ }
+
+ public static void cancelSingleVoicemailNotification(
+ @NonNull Context context, @Nullable Uri voicemailUri) {
+ LogUtil.enterBlock("VisualVoicemailNotifier.cancelSingleVoicemailNotification");
+ if (voicemailUri == null) {
+ LogUtil.e("VisualVoicemailNotifier.cancelSingleVoicemailNotification", "uri is null");
+ return;
+ }
+ // This will also dismiss the group summary if there are no more voicemail notifications.
+ DialerNotificationManager.cancel(
+ context, getNotificationTagForUri(voicemailUri), NOTIFICATION_ID);
+ }
+
+ private static String getNotificationTagForVoicemail(@NonNull NewCall voicemail) {
+ return getNotificationTagForUri(voicemail.voicemailUri);
+ }
+
+ private static String getNotificationTagForUri(@NonNull Uri voicemailUri) {
+ return NOTIFICATION_TAG_PREFIX + voicemailUri;
+ }
+
+ private static Notification.Builder createNotificationBuilder(@NonNull Context context) {
+ return new Notification.Builder(context)
+ .setSmallIcon(android.R.drawable.stat_notify_voicemail)
+ .setColor(context.getColor(R.color.dialer_theme_color))
+ .setGroup(GROUP_KEY)
+ .setOnlyAlertOnce(true)
+ .setAutoCancel(true);
+ }
+
+ private static Notification createNotificationForVoicemail(
+ @NonNull Context context,
+ @NonNull NewCall voicemail,
+ @NonNull Map<String, ContactInfo> contactInfos) {
+ PhoneAccountHandle handle = getAccountForCall(context, voicemail);
+ ContactInfo contactInfo = contactInfos.get(voicemail.number);
+
+ Notification.Builder builder =
+ createNotificationBuilder(context)
+ .setContentTitle(
+ context
+ .getResources()
+ .getQuantityString(R.plurals.notification_voicemail_title, 1, 1))
+ .setContentText(
+ ContactDisplayUtils.getTtsSpannedPhoneNumber(
+ context.getResources(),
+ R.string.notification_new_voicemail_ticker,
+ contactInfo.name))
+ .setWhen(voicemail.dateMs)
+ .setSound(getVoicemailRingtoneUri(context, handle))
+ .setDefaults(getNotificationDefaultFlags(context, handle));
+
+ if (voicemail.voicemailUri != null) {
+ builder.setDeleteIntent(
+ CallLogNotificationsService.createMarkSingleNewVoicemailAsOldIntent(
+ context, voicemail.voicemailUri));
+ }
+
+ if (BuildCompat.isAtLeastO()) {
+ builder.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle));
+ }
+
+ ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo);
+ Bitmap photoIcon = loader.loadPhotoIcon();
+ if (photoIcon != null) {
+ builder.setLargeIcon(photoIcon);
+ }
+ if (!TextUtils.isEmpty(voicemail.transcription)) {
+ Logger.get(context)
+ .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION);
+ builder.setStyle(new Notification.BigTextStyle().bigText(voicemail.transcription));
+ }
+ builder.setContentIntent(newVoicemailIntent(context, voicemail));
+ Logger.get(context).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED);
+ return builder.build();
+ }
+
+ @Nullable
+ private static Uri getVoicemailRingtoneUri(
+ @NonNull Context context, @Nullable PhoneAccountHandle handle) {
+ if (VERSION.SDK_INT < VERSION_CODES.N) {
+ return null;
+ }
+ if (handle == null) {
+ LogUtil.i("VisualVoicemailNotifier.getVoicemailRingtoneUri", "null handle, getting fallback");
+ handle = getFallbackAccount(context);
+ if (handle == null) {
+ LogUtil.i(
+ "VisualVoicemailNotifier.getVoicemailRingtoneUri",
+ "no fallback handle, using null (default) ringtone");
+ return null;
+ }
+ }
+ return context.getSystemService(TelephonyManager.class).getVoicemailRingtoneUri(handle);
+ }
+
+ private static int getNotificationDefaultFlags(
+ @NonNull Context context, @Nullable PhoneAccountHandle handle) {
+ if (VERSION.SDK_INT < VERSION_CODES.N) {
+ return Notification.DEFAULT_ALL;
+ }
+ if (handle == null) {
+ LogUtil.i(
+ "VisualVoicemailNotifier.getNotificationDefaultFlags", "null handle, getting fallback");
+ handle = getFallbackAccount(context);
+ if (handle == null) {
+ LogUtil.i(
+ "VisualVoicemailNotifier.getNotificationDefaultFlags",
+ "no fallback handle, using default vibration");
+ return Notification.DEFAULT_ALL;
+ }
+ }
+ if (context.getSystemService(TelephonyManager.class).isVoicemailVibrationEnabled(handle)) {
+ return Notification.DEFAULT_VIBRATE;
+ }
+ return 0;
+ }
+
+ private static PendingIntent newVoicemailIntent(
+ @NonNull Context context, @Nullable NewCall voicemail) {
+ Intent intent =
+ DialtactsActivity.getShowTabIntent(context, DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
+ // TODO (b/35486204): scroll to this voicemail
+ if (voicemail != null) {
+ intent.setData(voicemail.voicemailUri);
+ }
+ intent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true);
+ return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ /**
+ * Gets a phone account for the given call entry. This could be null if SIM associated with the
+ * entry is no longer in the device or for other reasons (for example, modem reboot).
+ */
+ @Nullable
+ public static PhoneAccountHandle getAccountForCall(
+ @NonNull Context context, @Nullable NewCall call) {
+ if (call == null || call.accountComponentName == null || call.accountId == null) {
+ return null;
+ }
+ return new PhoneAccountHandle(
+ ComponentName.unflattenFromString(call.accountComponentName), call.accountId);
+ }
+
+ /**
+ * Gets any available phone account that can be used to get sound settings for voicemail. This is
+ * only called if the phone account for the voicemail entry can't be found.
+ */
+ @Nullable
+ public static PhoneAccountHandle getFallbackAccount(@NonNull Context context) {
+ PhoneAccountHandle handle =
+ TelecomUtil.getDefaultOutgoingPhoneAccount(context, PhoneAccount.SCHEME_TEL);
+ if (handle == null) {
+ List<PhoneAccountHandle> handles = TelecomUtil.getCallCapablePhoneAccounts(context);
+ if (!handles.isEmpty()) {
+ handle = handles.get(0);
+ }
+ }
+ return handle;
+ }
+
+ private VisualVoicemailNotifier() {}
+}
diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
new file mode 100644
index 000000000..d6601be36
--- /dev/null
+++ b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2011 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.app.calllog;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import com.android.dialer.app.R;
+import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall;
+import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
+import com.android.dialer.blocking.FilteredNumbersUtil;
+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.DialerExecutors;
+import com.android.dialer.phonenumbercache.ContactInfo;
+import com.android.dialer.telecom.TelecomUtil;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** Updates voicemail notifications in the background. */
+class VisualVoicemailUpdateTask implements Worker<VisualVoicemailUpdateTask.Input, Void> {
+ @Nullable
+ @Override
+ public Void doInBackground(@NonNull Input input) throws Throwable {
+ updateNotification(input.context, input.queryHelper, input.queryHandler);
+ return null;
+ }
+
+ /**
+ * Updates the notification and notifies of the call with the given URI.
+ *
+ * <p>Clears the notification if there are no new voicemails, and notifies if the given URI
+ * corresponds to a new voicemail.
+ */
+ @WorkerThread
+ private static void updateNotification(
+ Context context,
+ CallLogNotificationsQueryHelper queryHelper,
+ FilteredNumberAsyncQueryHandler queryHandler) {
+ Assert.isWorkerThread();
+
+ List<NewCall> newCalls = queryHelper.getNewVoicemails();
+ if (newCalls == null) {
+ return;
+ }
+ newCalls = filterBlockedNumbers(context, queryHandler, newCalls);
+ if (newCalls.isEmpty()) {
+ return;
+ }
+
+ // This represents a list of names to include in the notification.
+ String callers = null;
+
+ // Maps each number into a name: if a number is in the map, it has already left a more
+ // recent voicemail.
+ Map<String, ContactInfo> contactInfos = new ArrayMap<>();
+ for (NewCall newCall : newCalls) {
+ if (!contactInfos.containsKey(newCall.number)) {
+ ContactInfo contactInfo =
+ queryHelper.getContactInfo(
+ newCall.number, newCall.numberPresentation, newCall.countryIso);
+ contactInfos.put(newCall.number, contactInfo);
+
+ // This is a new caller. Add it to the back of the list of callers.
+ if (TextUtils.isEmpty(callers)) {
+ callers = contactInfo.name;
+ } else {
+ callers =
+ context.getString(
+ R.string.notification_voicemail_callers_list, callers, contactInfo.name);
+ }
+ }
+ }
+ VisualVoicemailNotifier.showNotifications(context, newCalls, contactInfos, callers);
+ }
+
+ @WorkerThread
+ private static List<NewCall> filterBlockedNumbers(
+ Context context, FilteredNumberAsyncQueryHandler queryHandler, List<NewCall> newCalls) {
+ Assert.isWorkerThread();
+ if (FilteredNumbersUtil.hasRecentEmergencyCall(context)) {
+ LogUtil.i(
+ "VisualVoicemailUpdateTask.filterBlockedNumbers",
+ "not filtering due to recent emergency call");
+ return newCalls;
+ }
+
+ List<NewCall> result = new ArrayList<>();
+ for (NewCall newCall : newCalls) {
+ if (queryHandler.getBlockedIdSynchronous(newCall.number, newCall.countryIso) != null) {
+ LogUtil.i(
+ "VisualVoicemailUpdateTask.filterBlockedNumbers",
+ "found voicemail from blocked number, deleting");
+ if (newCall.voicemailUri != null) {
+ // Delete the voicemail.
+ CallLogAsyncTaskUtil.deleteVoicemailSynchronous(context, newCall.voicemailUri);
+ }
+ } else {
+ result.add(newCall);
+ }
+ }
+ return result;
+ }
+
+ /** Updates the voicemail notifications displayed. */
+ static void scheduleTask(@NonNull Context context, @NonNull Runnable callback) {
+ Assert.isNotNull(context);
+ Assert.isNotNull(callback);
+ if (!TelecomUtil.isDefaultDialer(context)) {
+ LogUtil.i("VisualVoicemailUpdateTask.scheduleTask", "not default dialer, not running");
+ callback.run();
+ return;
+ }
+
+ Input input =
+ new Input(
+ context,
+ CallLogNotificationsQueryHelper.getInstance(context),
+ new FilteredNumberAsyncQueryHandler(context));
+ DialerExecutors.createNonUiTaskBuilder(new VisualVoicemailUpdateTask())
+ .onSuccess(
+ output -> {
+ LogUtil.i("VisualVoicemailUpdateTask.scheduleTask", "update successful");
+ callback.run();
+ })
+ .onFailure(
+ throwable -> {
+ LogUtil.i("VisualVoicemailUpdateTask.scheduleTask", "update failed: " + throwable);
+ callback.run();
+ })
+ .build()
+ .executeParallel(input);
+ }
+
+ static class Input {
+ @NonNull final Context context;
+ @NonNull final CallLogNotificationsQueryHelper queryHelper;
+ @NonNull final FilteredNumberAsyncQueryHandler queryHandler;
+
+ Input(
+ Context context,
+ CallLogNotificationsQueryHelper queryHelper,
+ FilteredNumberAsyncQueryHandler queryHandler) {
+ this.context = context;
+ this.queryHelper = queryHelper;
+ this.queryHandler = queryHandler;
+ }
+ }
+}
diff --git a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
index 777f4c79f..2fbebdd30 100644
--- a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
+++ b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java
@@ -15,7 +15,6 @@
*/
package com.android.dialer.app.calllog;
-import android.app.NotificationManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -23,30 +22,49 @@ import android.content.Context;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import com.android.dialer.app.R;
+import android.support.annotation.WorkerThread;
import com.android.dialer.common.Assert;
-import com.android.dialer.notification.GroupedNotificationUtil;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.ThreadUtil;
/** Handles asynchronous queries to the call log for voicemail. */
public class VoicemailQueryHandler extends AsyncQueryHandler {
- private static final String TAG = "VoicemailQueryHandler";
-
/** The token for the query to mark all new voicemails as old. */
private static final int UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN = 50;
- private Context mContext;
-
@MainThread
- public VoicemailQueryHandler(Context context, ContentResolver contentResolver) {
+ private VoicemailQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
Assert.isMainThread();
- mContext = context;
+ }
+
+ @WorkerThread
+ public static void markAllNewVoicemailsAsRead(final @NonNull Context context) {
+ ThreadUtil.postOnUiThread(
+ () -> {
+ new VoicemailQueryHandler(context.getContentResolver()).markNewVoicemailsAsOld(null);
+ });
+ }
+
+ @WorkerThread
+ public static void markSingleNewVoicemailAsRead(
+ final @NonNull Context context, final Uri voicemailUri) {
+ if (voicemailUri == null) {
+ LogUtil.e("VoicemailQueryHandler.markSingleNewVoicemailAsRead", "voicemail URI is null");
+ return;
+ }
+ ThreadUtil.postOnUiThread(
+ () -> {
+ new VoicemailQueryHandler(context.getContentResolver())
+ .markNewVoicemailsAsOld(voicemailUri);
+ });
}
/** Updates all new voicemails to mark them as old. */
- public void markNewVoicemailsAsOld(@Nullable Uri voicemailUri) {
+ private void markNewVoicemailsAsOld(@Nullable Uri voicemailUri) {
// Mark all "new" voicemails as not new anymore.
StringBuilder where = new StringBuilder();
where.append(Calls.NEW);
@@ -70,11 +88,5 @@ public class VoicemailQueryHandler extends AsyncQueryHandler {
voicemailUri == null
? new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}
: new String[] {Integer.toString(Calls.VOICEMAIL_TYPE), voicemailUri.toString()});
-
- GroupedNotificationUtil.removeNotification(
- mContext.getSystemService(NotificationManager.class),
- voicemailUri != null ? voicemailUri.toString() : null,
- R.id.notification_visual_voicemail,
- DefaultVoicemailNotifier.VISUAL_VOICEMAIL_NOTIFICATION_TAG);
}
}
diff --git a/java/com/android/dialer/app/calllog/calllogcache/CallLogCache.java b/java/com/android/dialer/app/calllog/calllogcache/CallLogCache.java
index 7645a333e..15de14318 100644
--- a/java/com/android/dialer/app/calllog/calllogcache/CallLogCache.java
+++ b/java/com/android/dialer/app/calllog/calllogcache/CallLogCache.java
@@ -17,10 +17,16 @@
package com.android.dialer.app.calllog.calllogcache;
import android.content.Context;
+import android.support.annotation.Nullable;
import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import com.android.dialer.app.calllog.CallLogAdapter;
-import com.android.dialer.compat.CompatUtils;
+import com.android.dialer.calllogutils.PhoneAccountUtils;
+import com.android.dialer.telecom.TelecomUtil;
import com.android.dialer.util.CallUtil;
+import java.util.Map;
+import javax.annotation.concurrent.ThreadSafe;
/**
* This is the base class for the CallLogCaches.
@@ -31,7 +37,8 @@ import com.android.dialer.util.CallUtil;
*
* <p>This is designed with the specific use case of the {@link CallLogAdapter} in mind.
*/
-public abstract class CallLogCache {
+@ThreadSafe
+public class CallLogCache {
// TODO: Dialer should be fixed so as not to check isVoicemail() so often but at the time of
// this writing, that was a much larger undertaking than creating this cache.
@@ -39,20 +46,18 @@ public abstract class CallLogCache {
private boolean mHasCheckedForVideoAvailability;
private int mVideoAvailability;
+ private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new ArrayMap<>();
+ private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new ArrayMap<>();
+ private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new ArrayMap<>();
public CallLogCache(Context context) {
mContext = context;
}
- /** Return the most compatible version of the TelecomCallLogCache. */
- public static CallLogCache getCallLogCache(Context context) {
- if (CompatUtils.isClassAvailable("android.telecom.PhoneAccountHandle")) {
- return new CallLogCacheLollipopMr1(context);
- }
- return new CallLogCacheLollipop(context);
- }
-
- public void reset() {
+ public synchronized void reset() {
+ mPhoneAccountLabelCache.clear();
+ mPhoneAccountColorCache.clear();
+ mPhoneAccountCallWithNoteCache.clear();
mHasCheckedForVideoAvailability = false;
mVideoAvailability = 0;
}
@@ -61,19 +66,12 @@ public abstract class CallLogCache {
* Returns true if the given number is the number of the configured voicemail. To be able to
* mock-out this, it is not a static method.
*/
- public abstract boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number);
-
- /**
- * Returns {@code true} when the current sim supports video calls, regardless of the value in a
- * contact's {@link android.provider.ContactsContract.CommonDataKinds.Phone#CARRIER_PRESENCE}
- * column.
- */
- public boolean isVideoEnabled() {
- if (!mHasCheckedForVideoAvailability) {
- mVideoAvailability = CallUtil.getVideoCallingAvailability(mContext);
- mHasCheckedForVideoAvailability = true;
+ public synchronized boolean isVoicemailNumber(
+ PhoneAccountHandle accountHandle, @Nullable CharSequence number) {
+ if (TextUtils.isEmpty(number)) {
+ return false;
}
- return (mVideoAvailability & CallUtil.VIDEO_CALLING_ENABLED) != 0;
+ return TelecomUtil.isVoicemailNumber(mContext, accountHandle, number.toString());
}
/**
@@ -89,10 +87,26 @@ public abstract class CallLogCache {
}
/** Extract account label from PhoneAccount object. */
- public abstract String getAccountLabel(PhoneAccountHandle accountHandle);
+ public synchronized String getAccountLabel(PhoneAccountHandle accountHandle) {
+ if (mPhoneAccountLabelCache.containsKey(accountHandle)) {
+ return mPhoneAccountLabelCache.get(accountHandle);
+ } else {
+ String label = PhoneAccountUtils.getAccountLabel(mContext, accountHandle);
+ mPhoneAccountLabelCache.put(accountHandle, label);
+ return label;
+ }
+ }
/** Extract account color from PhoneAccount object. */
- public abstract int getAccountColor(PhoneAccountHandle accountHandle);
+ public synchronized int getAccountColor(PhoneAccountHandle accountHandle) {
+ if (mPhoneAccountColorCache.containsKey(accountHandle)) {
+ return mPhoneAccountColorCache.get(accountHandle);
+ } else {
+ Integer color = PhoneAccountUtils.getAccountColor(mContext, accountHandle);
+ mPhoneAccountColorCache.put(accountHandle, color);
+ return color;
+ }
+ }
/**
* Determines if the PhoneAccount supports specifying a call subject (i.e. calling with a note)
@@ -101,5 +115,14 @@ public abstract class CallLogCache {
* @param accountHandle The PhoneAccount handle.
* @return {@code true} if calling with a note is supported, {@code false} otherwise.
*/
- public abstract boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle);
+ public synchronized boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
+ if (mPhoneAccountCallWithNoteCache.containsKey(accountHandle)) {
+ return mPhoneAccountCallWithNoteCache.get(accountHandle);
+ } else {
+ Boolean supportsCallWithNote =
+ PhoneAccountUtils.getAccountSupportsCallSubject(mContext, accountHandle);
+ mPhoneAccountCallWithNoteCache.put(accountHandle, supportsCallWithNote);
+ return supportsCallWithNote;
+ }
+ }
}
diff --git a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipop.java b/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipop.java
deleted file mode 100644
index 78aaa4193..000000000
--- a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipop.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2015 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.app.calllog.calllogcache;
-
-import android.content.Context;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-
-/**
- * This is a compatibility class for the CallLogCache for versions of dialer before Lollipop Mr1
- * (the introduction of phone accounts).
- *
- * <p>This class should not be initialized directly and instead be acquired from {@link
- * CallLogCache#getCallLogCache}.
- */
-class CallLogCacheLollipop extends CallLogCache {
-
- private String mVoicemailNumber;
-
- /* package */ CallLogCacheLollipop(Context context) {
- super(context);
- }
-
- @Override
- public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) {
- if (TextUtils.isEmpty(number)) {
- return false;
- }
-
- String numberString = number.toString();
-
- if (!TextUtils.isEmpty(mVoicemailNumber)) {
- return PhoneNumberUtils.compare(numberString, mVoicemailNumber);
- }
-
- if (PhoneNumberUtils.isVoiceMailNumber(numberString)) {
- mVoicemailNumber = numberString;
- return true;
- }
-
- return false;
- }
-
- @Override
- public String getAccountLabel(PhoneAccountHandle accountHandle) {
- return null;
- }
-
- @Override
- public int getAccountColor(PhoneAccountHandle accountHandle) {
- return PhoneAccount.NO_HIGHLIGHT_COLOR;
- }
-
- @Override
- public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
- return false;
- }
-}
diff --git a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java b/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java
deleted file mode 100644
index 039998780..000000000
--- a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2013 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.app.calllog.calllogcache;
-
-import android.content.Context;
-import android.support.annotation.VisibleForTesting;
-import android.telecom.PhoneAccountHandle;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Pair;
-import com.android.dialer.calllogutils.PhoneAccountUtils;
-import com.android.dialer.phonenumberutil.PhoneNumberHelper;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * This is the CallLogCache for versions of dialer Lollipop Mr1 and above with support for multi-SIM
- * devices.
- *
- * <p>This class should not be initialized directly and instead be acquired from {@link
- * CallLogCache#getCallLogCache}.
- */
-class CallLogCacheLollipopMr1 extends CallLogCache {
-
- /*
- * Maps from a phone-account/number pair to a boolean because multiple numbers could return true
- * for the voicemail number if those numbers are not pre-normalized. Access must be synchronzied
- * as it's used in the background thread in CallLogAdapter. {@see CallLogAdapter#loadData}
- */
- @VisibleForTesting
- final Map<Pair<PhoneAccountHandle, CharSequence>, Boolean> mVoicemailQueryCache =
- new ConcurrentHashMap<>();
-
- private final Map<PhoneAccountHandle, String> mPhoneAccountLabelCache = new ArrayMap<>();
- private final Map<PhoneAccountHandle, Integer> mPhoneAccountColorCache = new ArrayMap<>();
- private final Map<PhoneAccountHandle, Boolean> mPhoneAccountCallWithNoteCache = new ArrayMap<>();
-
- /* package */ CallLogCacheLollipopMr1(Context context) {
- super(context);
- }
-
- @Override
- public void reset() {
- mVoicemailQueryCache.clear();
- mPhoneAccountLabelCache.clear();
- mPhoneAccountColorCache.clear();
- mPhoneAccountCallWithNoteCache.clear();
-
- super.reset();
- }
-
- @Override
- public boolean isVoicemailNumber(PhoneAccountHandle accountHandle, CharSequence number) {
- if (TextUtils.isEmpty(number)) {
- return false;
- }
-
- Pair<PhoneAccountHandle, CharSequence> key = new Pair<>(accountHandle, number);
- Boolean value = mVoicemailQueryCache.get(key);
- if (value != null) {
- return value;
- }
- boolean isVoicemail =
- PhoneNumberHelper.isVoicemailNumber(mContext, accountHandle, number.toString());
- mVoicemailQueryCache.put(key, isVoicemail);
- return isVoicemail;
- }
-
- @Override
- public String getAccountLabel(PhoneAccountHandle accountHandle) {
- if (mPhoneAccountLabelCache.containsKey(accountHandle)) {
- return mPhoneAccountLabelCache.get(accountHandle);
- } else {
- String label = PhoneAccountUtils.getAccountLabel(mContext, accountHandle);
- mPhoneAccountLabelCache.put(accountHandle, label);
- return label;
- }
- }
-
- @Override
- public int getAccountColor(PhoneAccountHandle accountHandle) {
- if (mPhoneAccountColorCache.containsKey(accountHandle)) {
- return mPhoneAccountColorCache.get(accountHandle);
- } else {
- Integer color = PhoneAccountUtils.getAccountColor(mContext, accountHandle);
- mPhoneAccountColorCache.put(accountHandle, color);
- return color;
- }
- }
-
- @Override
- public boolean doesAccountSupportCallSubject(PhoneAccountHandle accountHandle) {
- if (mPhoneAccountCallWithNoteCache.containsKey(accountHandle)) {
- return mPhoneAccountCallWithNoteCache.get(accountHandle);
- } else {
- Boolean supportsCallWithNote =
- PhoneAccountUtils.getAccountSupportsCallSubject(mContext, accountHandle);
- mPhoneAccountCallWithNoteCache.put(accountHandle, supportsCallWithNote);
- return supportsCallWithNote;
- }
- }
-}