diff options
Diffstat (limited to 'java/com/android/dialer')
12 files changed, 312 insertions, 132 deletions
diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml index 4200082a6..2ef5dad14 100644 --- a/java/com/android/dialer/app/AndroidManifest.xml +++ b/java/com/android/dialer/app/AndroidManifest.xml @@ -103,6 +103,11 @@ android:name="com.android.dialer.app.calllog.CallLogNotificationsService" /> + <service + android:name="com.android.dialer.app.calllog.VoicemailNotificationJobService" + android:permission="android.permission.BIND_JOB_SERVICE" + /> + <receiver android:directBootAware="true" android:name="com.android.dialer.app.calllog.MissedCallNotificationReceiver"> diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 13b6eb92c..7f5a9b94a 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -49,7 +49,6 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.view.DragEvent; import android.view.Gravity; -import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; @@ -169,6 +168,7 @@ public class DialtactsActivity extends TransactionSafeActivity private static final String TAG = "DialtactsActivity"; private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui"; private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui"; + private static final String KEY_IN_NEW_SEARCH_UI = "in_new_search_ui"; private static final String KEY_SEARCH_QUERY = "search_query"; private static final String KEY_FIRST_LAUNCH = "first_launch"; private static final String KEY_WAS_CONFIGURATION_CHANGE = "was_configuration_change"; @@ -213,6 +213,8 @@ public class DialtactsActivity extends TransactionSafeActivity */ private boolean mStateSaved; + private boolean mIsKeyboardOpen; + private boolean mInNewSearch; private boolean mIsRestarting; private boolean mInDialpadSearch; private boolean mInRegularSearch; @@ -330,27 +332,6 @@ public class DialtactsActivity extends TransactionSafeActivity private int mActionBarHeight; private int mPreviouslySelectedTabIndex; - /** Handles the user closing the soft keyboard. */ - private final View.OnKeyListener mSearchEditTextLayoutListener = - new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { - if (TextUtils.isEmpty(mSearchView.getText().toString())) { - // If the search term is empty, close the search UI. - PerformanceReport.recordClick(UiAction.Type.CLOSE_SEARCH_WITH_HIDE_BUTTON); - maybeExitSearchUi(); - } else { - // If the search term is not empty, show the dialpad fab. - if (!mFloatingActionButtonController.isVisible()) { - PerformanceReport.recordClick(UiAction.Type.HIDE_KEYBOARD_IN_SEARCH); - } - showFabInSearchUi(); - } - } - return false; - } - }; /** * The text returned from a voice search query. Set in {@link #onActivityResult} and used in @@ -410,30 +391,20 @@ public class DialtactsActivity extends TransactionSafeActivity SearchEditTextLayout searchEditTextLayout = actionBar.getCustomView().findViewById(R.id.search_view_container); - searchEditTextLayout.setPreImeKeyListener(mSearchEditTextLayoutListener); mActionBarController = new ActionBarController(this, searchEditTextLayout); mSearchView = searchEditTextLayout.findViewById(R.id.search_view); mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); mSearchView.setHint(getSearchBoxHint()); + mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button); searchEditTextLayout .findViewById(R.id.search_box_collapsed) .setOnClickListener(mSearchViewOnClickListener); - searchEditTextLayout.setCallback( - new SearchEditTextLayout.Callback() { - @Override - public void onBackButtonClicked() { - onBackPressed(); - } - - @Override - public void onSearchViewClicked() { - // Hide FAB, as the keyboard is shown. - mFloatingActionButtonController.scaleOut(); - } - }); + searchEditTextLayout + .findViewById(R.id.search_back_button) + .setOnClickListener(v -> exitSearchUi()); mIsLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; @@ -460,6 +431,7 @@ public class DialtactsActivity extends TransactionSafeActivity mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY); mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI); mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI); + mInNewSearch = savedInstanceState.getBoolean(KEY_IN_NEW_SEARCH_UI); mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH); mWasConfigurationChange = savedInstanceState.getBoolean(KEY_WAS_CONFIGURATION_CHANGE); mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN); @@ -654,6 +626,7 @@ public class DialtactsActivity extends TransactionSafeActivity outState.putString(KEY_SEARCH_QUERY, mSearchQuery); outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch); outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch); + outState.putBoolean(KEY_IN_NEW_SEARCH_UI, mInNewSearch); outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch); outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown); outState.putBoolean(KEY_WAS_CONFIGURATION_CHANGE, isChangingConfigurations()); @@ -893,14 +866,19 @@ public class DialtactsActivity extends TransactionSafeActivity updateSearchFragmentPosition(); } + @Override + public void onCallPlacedFromDialpad() { + hideDialpadFragment(false /* animate */, true /*clearDialpad */); + exitSearchUi(); + } + /** * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in a * callback after the hide animation ends. * * @see #commitDialpadFragmentHide */ - @Override - public void hideDialpadFragment(boolean animate, boolean clearDialpad) { + private void hideDialpadFragment(boolean animate, boolean clearDialpad) { LogUtil.enterBlock("DialtactsActivity.hideDialpadFragment"); if (mDialpadFragment == null || mDialpadFragment.getView() == null) { return; @@ -935,11 +913,6 @@ public class DialtactsActivity extends TransactionSafeActivity mActionBarController.onDialpadDown(); - if (isInSearchUi()) { - if (TextUtils.isEmpty(mSearchQuery)) { - exitSearchUi(); - } - } // reset the title to normal. setTitle(R.string.launcherActivityLabel); } @@ -987,7 +960,7 @@ public class DialtactsActivity extends TransactionSafeActivity @Override public boolean isInSearchUi() { - return mInDialpadSearch || mInRegularSearch; + return mInDialpadSearch || mInRegularSearch || mInNewSearch; } @Override @@ -998,6 +971,7 @@ public class DialtactsActivity extends TransactionSafeActivity private void setNotInSearchUi() { mInDialpadSearch = false; mInRegularSearch = false; + mInNewSearch = false; } private void hideDialpadAndSearchUi() { @@ -1171,17 +1145,21 @@ public class DialtactsActivity extends TransactionSafeActivity } final String tag; + mInDialpadSearch = false; + mInRegularSearch = false; + mInNewSearch = false; boolean useNewSearch = ConfigProviderBindings.get(this).getBoolean("enable_new_search_fragment", false); if (useNewSearch) { tag = TAG_NEW_SEARCH_FRAGMENT; + mInNewSearch = true; } else if (smartDialSearch) { tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; + mInDialpadSearch = true; } else { tag = TAG_REGULAR_SEARCH_FRAGMENT; + mInRegularSearch = true; } - mInDialpadSearch = smartDialSearch; - mInRegularSearch = !smartDialSearch; mFloatingActionButtonController.scaleOut(); @@ -1304,43 +1282,34 @@ public class DialtactsActivity extends TransactionSafeActivity return; } if (mIsDialpadShown) { - if (TextUtils.isEmpty(mSearchQuery) - || (mSmartDialSearchFragment != null - && mSmartDialSearchFragment.isVisible() - && mSmartDialSearchFragment.getAdapter().getCount() == 0)) { - exitSearchUi(); - } hideDialpadFragment(true, false); } else if (isInSearchUi()) { - exitSearchUi(); - DialerUtils.hideInputMethod(mParentLayout); + if (mIsKeyboardOpen) { + DialerUtils.hideInputMethod(mParentLayout); + PerformanceReport.recordClick(UiAction.Type.HIDE_KEYBOARD_IN_SEARCH); + } else { + exitSearchUi(); + } } else { super.onBackPressed(); } } - private void maybeEnterSearchUi() { - if (!isInSearchUi()) { - enterSearchUi(true /* isSmartDial */, mSearchQuery, false); + @Override + public void onConfigurationChanged(Configuration configuration) { + super.onConfigurationChanged(configuration); + // Checks whether a hardware keyboard is available + if (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) { + mIsKeyboardOpen = true; + } else if (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { + mIsKeyboardOpen = false; } } - /** @return True if the search UI was exited, false otherwise */ - private boolean maybeExitSearchUi() { - if (isInSearchUi() && TextUtils.isEmpty(mSearchQuery)) { - exitSearchUi(); - DialerUtils.hideInputMethod(mParentLayout); - return true; + private void maybeEnterSearchUi() { + if (!isInSearchUi()) { + enterSearchUi(true /* isSmartDial */, mSearchQuery, false); } - return false; - } - - private void showFabInSearchUi() { - mFloatingActionButtonController.changeIcon( - getResources().getDrawable(R.drawable.quantum_ic_dialpad_white_24, null), - getResources().getString(R.string.action_menu_dialpad_button)); - mFloatingActionButtonController.align(getFabAlignment(), false /* animate */); - mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS); } @Override diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java index c749b65ba..2f8b1f476 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java @@ -24,7 +24,7 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; -import android.os.Build.VERSION_CODES; +import android.os.Build; import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -35,14 +35,17 @@ import android.text.TextUtils; import com.android.dialer.app.R; import com.android.dialer.calllogutils.PhoneNumberDisplayUtil; import com.android.dialer.common.LogUtil; +import com.android.dialer.compat.android.provider.VoicemailCompat; import com.android.dialer.location.GeoUtil; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumbercache.ContactInfoHelper; import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** Helper class operating on call log notifications. */ +@TargetApi(Build.VERSION_CODES.M) public class CallLogNotificationsQueryHelper { private final Context mContext; @@ -133,6 +136,10 @@ public class CallLogNotificationsQueryHelper { return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver); } + NewCallsQuery getNewCallsQuery() { + return mNewCallsQuery; + } + /** * Get all voicemails with the "new" flag set to 1. * @@ -216,6 +223,10 @@ public class CallLogNotificationsQueryHelper { /** Returns the new calls of a certain type for which a notification should be generated. */ @Nullable List<NewCall> query(int type); + + /** Returns a {@link NewCall} pointed by the {@code callsUri} */ + @Nullable + NewCall query(Uri callsUri); } /** Information about a new voicemail. */ @@ -230,6 +241,7 @@ public class CallLogNotificationsQueryHelper { public final String transcription; public final String countryIso; public final long dateMs; + public final int transcriptionState; public NewCall( Uri callsUri, @@ -240,7 +252,8 @@ public class CallLogNotificationsQueryHelper { String accountId, String transcription, String countryIso, - long dateMs) { + long dateMs, + int transcriptionState) { this.callsUri = callsUri; this.voicemailUri = voicemailUri; this.number = number; @@ -250,6 +263,7 @@ public class CallLogNotificationsQueryHelper { this.transcription = transcription; this.countryIso = countryIso; this.dateMs = dateMs; + this.transcriptionState = transcriptionState; } } @@ -270,6 +284,16 @@ public class CallLogNotificationsQueryHelper { Calls.COUNTRY_ISO, Calls.DATE }; + + private static final String[] PROJECTION_O; + + static { + List<String> list = new ArrayList<>(); + list.addAll(Arrays.asList(PROJECTION)); + list.add(VoicemailCompat.TRANSCRIPTION_STATE); + PROJECTION_O = list.toArray(new String[list.size()]); + } + private static final int ID_COLUMN_INDEX = 0; private static final int NUMBER_COLUMN_INDEX = 1; private static final int VOICEMAIL_URI_COLUMN_INDEX = 2; @@ -279,6 +303,7 @@ public class CallLogNotificationsQueryHelper { private static final int TRANSCRIPTION_COLUMN_INDEX = 6; private static final int COUNTRY_ISO_COLUMN_INDEX = 7; private static final int DATE_COLUMN_INDEX = 8; + private static final int TRANSCRIPTION_STATE_COLUMN_INDEX = 9; private final ContentResolver mContentResolver; private final Context mContext; @@ -290,7 +315,7 @@ public class CallLogNotificationsQueryHelper { @Override @Nullable - @TargetApi(VERSION_CODES.M) + @TargetApi(Build.VERSION_CODES.M) public List<NewCall> query(int type) { if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) { LogUtil.w( @@ -309,7 +334,7 @@ public class CallLogNotificationsQueryHelper { try (Cursor cursor = mContentResolver.query( Calls.CONTENT_URI_WITH_VOICEMAIL, - PROJECTION, + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? PROJECTION_O : PROJECTION, selection, selectionArgs, Calls.DEFAULT_SORT_ORDER)) { @@ -329,6 +354,26 @@ public class CallLogNotificationsQueryHelper { } } + @Nullable + @Override + public NewCall query(Uri callsUri) { + if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) { + LogUtil.w( + "CallLogNotificationsQueryHelper.DefaultNewCallsQuery.query", + "No READ_CALL_LOG permission, returning null for calls lookup."); + return null; + } + try (Cursor cursor = mContentResolver.query(callsUri, PROJECTION, null, null, null)) { + if (cursor == null) { + return null; + } + if (!cursor.moveToFirst()) { + return null; + } + return createNewCallsFromCursor(cursor); + } + } + /** Returns an instance of {@link NewCall} created by using the values of the cursor. */ private NewCall createNewCallsFromCursor(Cursor cursor) { String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX); @@ -345,7 +390,10 @@ public class CallLogNotificationsQueryHelper { cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX), cursor.getString(TRANSCRIPTION_COLUMN_INDEX), cursor.getString(COUNTRY_ISO_COLUMN_INDEX), - cursor.getLong(DATE_COLUMN_INDEX)); + cursor.getLong(DATE_COLUMN_INDEX), + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + ? cursor.getInt(TRANSCRIPTION_STATE_COLUMN_INDEX) + : VoicemailCompat.TRANSCRIPTION_NOT_STARTED); } } } diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index b363b5ab6..de766191a 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -47,6 +47,7 @@ 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.compat.android.provider.VoicemailCompat; import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.notification.NotificationChannelId; import com.android.dialer.notification.NotificationManagerUtils; @@ -153,7 +154,8 @@ public class MissedCallNotifier implements Worker<Pair<Integer, String>, Void> { null, null, null, - System.currentTimeMillis()); + System.currentTimeMillis(), + VoicemailCompat.TRANSCRIPTION_NOT_STARTED); // TODO: look up caller ID that is not in contacts. ContactInfo contactInfo = diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java index cbadfd317..ceae3d38e 100644 --- a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java @@ -27,7 +27,6 @@ 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; @@ -39,6 +38,7 @@ 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.compat.android.provider.VoicemailCompat; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; import com.android.dialer.notification.DialerNotificationManager; @@ -54,9 +54,9 @@ 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; + static final int NOTIFICATION_ID = 1; /** Tag for the group summary notification. */ - private static final String GROUP_SUMMARY_NOTIFICATION_TAG = "GroupSummary_VisualVoicemail"; + 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. @@ -84,7 +84,7 @@ final class VisualVoicemailNotifier { .setGroupSummary(true) .setContentIntent(newVoicemailIntent(context, null)); - if (BuildCompat.isAtLeastO()) { + if (VERSION.SDK_INT >= VERSION_CODES.O) { groupSummary.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN); PhoneAccountHandle handle = getAccountForCall(context, newCalls.get(0)); groupSummary.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)); @@ -136,7 +136,7 @@ final class VisualVoicemailNotifier { .setAutoCancel(true); } - private static Notification createNotificationForVoicemail( + static Notification createNotificationForVoicemail( @NonNull Context context, @NonNull NewCall voicemail, @NonNull Map<String, ContactInfo> contactInfos) { @@ -146,10 +146,6 @@ final class VisualVoicemailNotifier { 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, @@ -158,13 +154,51 @@ final class VisualVoicemailNotifier { .setSound(getVoicemailRingtoneUri(context, handle)) .setDefaults(getNotificationDefaultFlags(context, handle)); + if (!TextUtils.isEmpty(voicemail.transcription)) { + Logger.get(context) + .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION); + builder.setContentText(voicemail.transcription); + } else { + switch (voicemail.transcriptionState) { + case VoicemailCompat.TRANSCRIPTION_IN_PROGRESS: + Logger.get(context) + .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_IN_PROGRESS); + builder.setContentText(context.getString(R.string.voicemail_transcription_in_progress)); + break; + case VoicemailCompat.TRANSCRIPTION_FAILED_NO_SPEECH_DETECTED: + Logger.get(context) + .logImpression( + DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE); + builder.setContentText( + context.getString(R.string.voicemail_transcription_failed_no_speech)); + break; + case VoicemailCompat.TRANSCRIPTION_FAILED_LANGUAGE_NOT_SUPPORTED: + Logger.get(context) + .logImpression( + DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE); + builder.setContentText( + context.getString(R.string.voicemail_transcription_failed_language_not_supported)); + break; + case VoicemailCompat.TRANSCRIPTION_FAILED: + Logger.get(context) + .logImpression( + DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE); + builder.setContentText(context.getString(R.string.voicemail_transcription_failed)); + break; + default: + Logger.get(context) + .logImpression(DialerImpression.Type.VVM_NOTIFICATION_CREATED_WITH_NO_TRANSCRIPTION); + break; + } + } + if (voicemail.voicemailUri != null) { builder.setDeleteIntent( CallLogNotificationsService.createMarkSingleNewVoicemailAsOldIntent( context, voicemail.voicemailUri)); } - if (BuildCompat.isAtLeastO()) { + if (VERSION.SDK_INT >= VERSION_CODES.O) { builder.setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle)); } @@ -173,11 +207,6 @@ final class VisualVoicemailNotifier { 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(); diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java index d6601be36..219ad676d 100644 --- a/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java +++ b/java/com/android/dialer/app/calllog/VisualVoicemailUpdateTask.java @@ -17,6 +17,8 @@ package com.android.dialer.app.calllog; import android.content.Context; +import android.net.Uri; +import android.service.notification.StatusBarNotification; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; @@ -30,6 +32,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; import com.android.dialer.common.concurrent.DialerExecutors; +import com.android.dialer.notification.DialerNotificationManager; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.telecom.TelecomUtil; import java.util.ArrayList; @@ -57,13 +60,20 @@ class VisualVoicemailUpdateTask implements Worker<VisualVoicemailUpdateTask.Inpu CallLogNotificationsQueryHelper queryHelper, FilteredNumberAsyncQueryHandler queryHandler) { Assert.isWorkerThread(); + LogUtil.enterBlock("VisualVoicemailUpdateTask.updateNotification"); - List<NewCall> newCalls = queryHelper.getNewVoicemails(); - if (newCalls == null) { + List<NewCall> voicemailsToNotify = queryHelper.getNewVoicemails(); + if (voicemailsToNotify == null) { + // Query failed, just return return; } - newCalls = filterBlockedNumbers(context, queryHandler, newCalls); - if (newCalls.isEmpty()) { + + voicemailsToNotify.addAll(getAndUpdateVoicemailsWithExistingNotification(context, queryHelper)); + voicemailsToNotify = filterBlockedNumbers(context, queryHandler, voicemailsToNotify); + if (voicemailsToNotify.isEmpty()) { + LogUtil.i("VisualVoicemailUpdateTask.updateNotification", "no voicemails to notify about"); + VisualVoicemailNotifier.cancelAllVoicemailNotifications(context); + VoicemailNotificationJobService.cancelJob(context); return; } @@ -73,7 +83,7 @@ class VisualVoicemailUpdateTask implements Worker<VisualVoicemailUpdateTask.Inpu // 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) { + for (NewCall newCall : voicemailsToNotify) { if (!contactInfos.containsKey(newCall.number)) { ContactInfo contactInfo = queryHelper.getContactInfo( @@ -90,7 +100,43 @@ class VisualVoicemailUpdateTask implements Worker<VisualVoicemailUpdateTask.Inpu } } } - VisualVoicemailNotifier.showNotifications(context, newCalls, contactInfos, callers); + VisualVoicemailNotifier.showNotifications(context, voicemailsToNotify, contactInfos, callers); + + // Set trigger to update notifications when database changes. + VoicemailNotificationJobService.scheduleJob(context); + } + + /** + * Cancel notification for voicemail that is already deleted. Returns a list of voicemails that + * already has notifications posted and should be updated. + */ + @WorkerThread + @NonNull + private static List<NewCall> getAndUpdateVoicemailsWithExistingNotification( + Context context, CallLogNotificationsQueryHelper queryHelper) { + Assert.isWorkerThread(); + List<NewCall> result = new ArrayList<>(); + for (StatusBarNotification notification : + DialerNotificationManager.getActiveNotifications(context)) { + if (notification.getId() != VisualVoicemailNotifier.NOTIFICATION_ID) { + continue; + } + if (TextUtils.equals( + notification.getTag(), VisualVoicemailNotifier.GROUP_SUMMARY_NOTIFICATION_TAG)) { + // Group header + continue; + } + NewCall existingCall = queryHelper.getNewCallsQuery().query(Uri.parse(notification.getTag())); + if (existingCall != null) { + result.add(existingCall); + } else { + LogUtil.i( + "VisualVoicemailUpdateTask.getVoicemailsWithExistingNotification", + "voicemail deleted, removing notification"); + DialerNotificationManager.cancel(context, notification.getTag(), notification.getId()); + } + } + return result; } @WorkerThread diff --git a/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java b/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java new file mode 100644 index 000000000..ba61601ae --- /dev/null +++ b/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java @@ -0,0 +1,89 @@ +/* + * 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.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.Build; +import android.provider.VoicemailContract; +import com.android.dialer.common.LogUtil; +import com.android.dialer.constants.ScheduledJobIds; + +/** Monitors voicemail provider changes to update active notifications. */ +public class VoicemailNotificationJobService extends JobService { + + private static JobInfo jobInfo; + + /** + * Start monitoring the provider. The provider should be monitored whenever a visual voicemail + * notification is visible. + */ + public static void scheduleJob(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + LogUtil.i("VoicemailNotificationJobService.scheduleJob", "not supported"); + } else { + context.getSystemService(JobScheduler.class).schedule(getJobInfo(context)); + LogUtil.i("VoicemailNotificationJobService.scheduleJob", "job scheduled"); + } + } + + /** + * Stop monitoring the provider. The provider should not be monitored when visual voicemail + * notification is cleared. + */ + public static void cancelJob(Context context) { + context.getSystemService(JobScheduler.class).cancel(ScheduledJobIds.VVM_NOTIFICATION_JOB); + LogUtil.i("VoicemailNotificationJobService.scheduleJob", "job canceled"); + } + + @Override + public boolean onStartJob(JobParameters params) { + LogUtil.i("VoicemailNotificationJobService.onStartJob", "updating notification"); + VisualVoicemailUpdateTask.scheduleTask( + this, + () -> { + jobFinished(params, false); + }); + return true; // Running in background + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } + + private static JobInfo getJobInfo(Context context) { + if (jobInfo == null) { + jobInfo = + new JobInfo.Builder( + ScheduledJobIds.VVM_NOTIFICATION_JOB, + new ComponentName(context, VoicemailNotificationJobService.class)) + .addTriggerContentUri( + new JobInfo.TriggerContentUri( + VoicemailContract.Voicemails.CONTENT_URI, + JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) + .setTriggerContentMaxDelay(0) + .build(); + } + + return jobInfo; + } +} diff --git a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java index 2fbebdd30..169d0fd35 100644 --- a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java +++ b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java @@ -45,7 +45,8 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { public static void markAllNewVoicemailsAsRead(final @NonNull Context context) { ThreadUtil.postOnUiThread( () -> { - new VoicemailQueryHandler(context.getContentResolver()).markNewVoicemailsAsOld(null); + new VoicemailQueryHandler(context.getContentResolver()) + .markNewVoicemailsAsOld(context, null); }); } @@ -59,12 +60,12 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { ThreadUtil.postOnUiThread( () -> { new VoicemailQueryHandler(context.getContentResolver()) - .markNewVoicemailsAsOld(voicemailUri); + .markNewVoicemailsAsOld(context, voicemailUri); }); } /** Updates all new voicemails to mark them as old. */ - private void markNewVoicemailsAsOld(@Nullable Uri voicemailUri) { + private void markNewVoicemailsAsOld(Context context, @Nullable Uri voicemailUri) { // Mark all "new" voicemails as not new anymore. StringBuilder where = new StringBuilder(); where.append(Calls.NEW); @@ -88,5 +89,8 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { voicemailUri == null ? new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)} : new String[] {Integer.toString(Calls.VOICEMAIL_TYPE), voicemailUri.toString()}); + + // No more notifications, stop monitoring the voicemail provider + VoicemailNotificationJobService.cancelJob(context); } } diff --git a/java/com/android/dialer/app/widget/ActionBarController.java b/java/com/android/dialer/app/widget/ActionBarController.java index c1b4cc2b4..3daa0e2d4 100644 --- a/java/com/android/dialer/app/widget/ActionBarController.java +++ b/java/com/android/dialer/app/widget/ActionBarController.java @@ -49,18 +49,6 @@ public class ActionBarController { } }; - private final AnimationCallback mFadeInCallback = - new AnimationCallback() { - @Override - public void onAnimationEnd() { - slideActionBar(false /* slideUp */, false /* animate */); - } - - @Override - public void onAnimationCancel() { - slideActionBar(false /* slideUp */, false /* animate */); - } - }; private ValueAnimator mAnimator; public ActionBarController(ActivityUi activityUi, SearchEditTextLayout searchBox) { @@ -112,17 +100,13 @@ public class ActionBarController { mSearchBox.isFadedOut(), mSearchBox.isExpanded()); if (mActivityUi.isInSearchUi()) { - if (mActivityUi.hasSearchQuery()) { - if (mSearchBox.isFadedOut()) { - mSearchBox.setVisible(true); - } - if (!mSearchBox.isExpanded()) { - mSearchBox.expand(false /* animate */, false /* requestFocus */); - } - slideActionBar(false /* slideUp */, true /* animate */); - } else { - mSearchBox.fadeIn(mFadeInCallback); + if (mSearchBox.isFadedOut()) { + mSearchBox.setVisible(true); + } + if (!mSearchBox.isExpanded()) { + mSearchBox.expand(false /* animate */, false /* requestFocus */); } + slideActionBar(false /* slideUp */, true /* animate */); } } diff --git a/java/com/android/dialer/constants/ScheduledJobIds.java b/java/com/android/dialer/constants/ScheduledJobIds.java index cf93a464a..3fcbb0c2e 100644 --- a/java/com/android/dialer/constants/ScheduledJobIds.java +++ b/java/com/android/dialer/constants/ScheduledJobIds.java @@ -34,6 +34,7 @@ public final class ScheduledJobIds { public static final int VVM_DEVICE_PROVISIONED_JOB = 202; public static final int VVM_TRANSCRIPTION_JOB = 203; public static final int VVM_TRANSCRIPTION_BACKFILL_JOB = 204; + public static final int VVM_NOTIFICATION_JOB = 205; public static final int VOIP_REGISTRATION = 300; diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index 86a83796e..837c3af90 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -1001,12 +1001,12 @@ public class DialpadFragment extends Fragment DialerUtils.startActivityWithErrorToast( getActivity(), new CallIntentBuilder(CallUtil.getVoicemailUri(), CallInitiationType.Type.DIALPAD).build()); - hideAndClearDialpad(false); + hideAndClearDialpad(); } - private void hideAndClearDialpad(boolean animate) { + private void hideAndClearDialpad() { LogUtil.enterBlock("DialpadFragment.hideAndClearDialpad"); - FragmentUtils.getParentUnsafe(this, DialpadListener.class).hideDialpadFragment(animate, true); + FragmentUtils.getParentUnsafe(this, DialpadListener.class).onCallPlacedFromDialpad(); } /** @@ -1053,7 +1053,7 @@ public class DialpadFragment extends Fragment final Intent intent = new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD).build(); DialerUtils.startActivityWithErrorToast(getActivity(), intent); - hideAndClearDialpad(false); + hideAndClearDialpad(); } } } @@ -1297,7 +1297,7 @@ public class DialpadFragment extends Fragment return true; } else if (resId == R.id.menu_call_with_note) { CallSubjectDialog.start(getActivity(), mDigits.getText().toString()); - hideAndClearDialpad(false); + hideAndClearDialpad(); return true; } else { return false; @@ -1710,7 +1710,7 @@ public class DialpadFragment extends Fragment void onDialpadShown(); - void hideDialpadFragment(boolean animate, boolean value); + void onCallPlacedFromDialpad(); } /** Callback for async lookup of the last number dialed. */ diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index 94af6c3fd..154460ccb 100644 --- a/java/com/android/dialer/logging/dialer_impression.proto +++ b/java/com/android/dialer/logging/dialer_impression.proto @@ -532,7 +532,10 @@ message DialerImpression { IN_CALL_DIALPAD_CLOSE_BUTTON_PRESSED = 1267; // More voicemail transcription impressions - VVM_TRANSCRIPTION_JOB_STOPPED = 1268; - VVM_TRANSCRIPTION_TASK_CANCELLED = 1269; + VVM_NOTIFICATION_CREATED_WITH_IN_PROGRESS = 1268; + VVM_NOTIFICATION_CREATED_WITH_TRANSCRIPTION_FAILURE = 1269; + VVM_NOTIFICATION_CREATED_WITH_NO_TRANSCRIPTION = 1270; + VVM_TRANSCRIPTION_JOB_STOPPED = 1271; + VVM_TRANSCRIPTION_TASK_CANCELLED = 1272; } } |