From d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9 Mon Sep 17 00:00:00 2001 From: Eric Erfanian Date: Wed, 15 Mar 2017 14:41:07 -0700 Subject: Update Dialer source from latest green build. * Refactor voicemail component * Add new enriched calling components Test: treehugger, manual aosp testing Change-Id: I521a0f86327d4b42e14d93927c7d613044ed5942 --- java/com/android/dialer/app/AndroidManifest.xml | 12 +- .../com/android/dialer/app/CallDetailActivity.java | 480 --------------------- java/com/android/dialer/app/DialerApplication.java | 77 ---- java/com/android/dialer/app/DialtactsActivity.java | 87 ++-- java/com/android/dialer/app/PhoneCallDetails.java | 207 --------- .../android/dialer/app/SpecialCharSequenceMgr.java | 46 +- .../app/calllog/CallDetailHistoryAdapter.java | 214 --------- .../dialer/app/calllog/CallLogActivity.java | 220 ++++++++++ .../android/dialer/app/calllog/CallLogAdapter.java | 94 +++- .../dialer/app/calllog/CallLogAsyncTaskUtil.java | 243 +---------- .../dialer/app/calllog/CallLogFragment.java | 93 +++- .../dialer/app/calllog/CallLogListItemHelper.java | 11 +- .../app/calllog/CallLogListItemViewHolder.java | 72 ++-- .../app/calllog/CallLogNotificationsHelper.java | 299 ------------- .../calllog/CallLogNotificationsQueryHelper.java | 334 ++++++++++++++ .../app/calllog/CallLogNotificationsService.java | 80 ++-- .../dialer/app/calllog/CallLogReceiver.java | 4 +- .../android/dialer/app/calllog/CallTypeHelper.java | 136 ------ .../dialer/app/calllog/CallTypeIconsView.java | 221 ---------- .../app/calllog/DefaultVoicemailNotifier.java | 264 ++++++------ .../android/dialer/app/calllog/IntentProvider.java | 27 +- .../dialer/app/calllog/MissedCallNotifier.java | 405 ++++++++++------- .../dialer/app/calllog/PhoneAccountHandles.java | 41 ++ .../dialer/app/calllog/PhoneAccountUtils.java | 104 ----- .../dialer/app/calllog/PhoneCallDetailsHelper.java | 5 +- .../dialer/app/calllog/PhoneCallDetailsViews.java | 1 + .../dialer/app/calllog/PhoneNumberDisplayUtil.java | 85 ---- .../calllog/VisualVoicemailCallLogFragment.java | 5 +- .../dialer/app/calllog/VoicemailQueryHandler.java | 25 +- .../calllogcache/CallLogCacheLollipopMr1.java | 10 +- .../dialer/app/contactinfo/ContactInfoCache.java | 4 +- .../dialer/app/contactinfo/ContactPhotoLoader.java | 2 +- .../dialer/app/dialpad/DialpadFragment.java | 4 +- .../BlockedNumbersSettingsActivity.java | 5 - .../app/legacybindings/DialerLegacyBindings.java | 3 + .../legacybindings/DialerLegacyBindingsStub.java | 4 + .../com/android/dialer/app/list/ListsFragment.java | 26 +- .../android/dialer/app/list/SearchFragment.java | 11 +- .../app/manifests/activities/AndroidManifest.xml | 12 - .../dialer/app/res/drawable-hdpi/ic_call_arrow.png | Bin 538 -> 0 bytes .../dialer/app/res/drawable-mdpi/ic_call_arrow.png | Bin 455 -> 0 bytes .../app/res/drawable-xhdpi/ic_call_arrow.png | Bin 627 -> 0 bytes .../app/res/drawable-xxhdpi/ic_call_arrow.png | Bin 1203 -> 0 bytes .../app/res/drawable-xxxhdpi/ic_call_arrow.png | Bin 1344 -> 0 bytes .../app/res/drawable-xxxhdpi/search_shadow.9.png | Bin 0 -> 1148 bytes .../android/dialer/app/res/layout/call_detail.xml | 32 -- .../dialer/app/res/layout/call_detail_footer.xml | 52 --- .../dialer/app/res/layout/call_detail_header.xml | 89 ---- .../app/res/layout/call_detail_history_item.xml | 3 +- .../dialer/app/res/layout/call_log_activity.xml | 40 ++ .../dialer/app/res/layout/call_log_list_item.xml | 3 +- .../dialer/app/res/menu/call_log_options.xml | 22 + .../dialer/app/res/menu/dialtacts_options.xml | 8 +- java/com/android/dialer/app/res/values/colors.xml | 8 - java/com/android/dialer/app/res/values/dimens.xml | 5 +- java/com/android/dialer/app/res/values/strings.xml | 84 +--- java/com/android/dialer/app/res/values/styles.xml | 5 - .../app/settings/DialerSettingsActivity.java | 13 + .../app/voicemail/VoicemailPlaybackLayout.java | 7 - .../app/voicemail/VoicemailPlaybackPresenter.java | 84 ++-- .../error/OmtpVoicemailMessageCreator.java | 113 ++++- .../app/voicemail/error/VoicemailErrorMessage.java | 51 +++ .../error/VoicemailErrorMessageCreator.java | 2 +- .../app/voicemail/error/VoicemailStatus.java | 7 + .../error/Vvm3VoicemailMessageCreator.java | 2 +- .../res/layout/voicemai_error_message_fragment.xml | 1 - .../error/res/layout/voicemail_tos_fragment.xml | 1 - .../app/voicemail/error/res/values/strings.xml | 7 + .../dialer/app/widget/ActionBarController.java | 88 +--- java/com/android/dialer/backup/AndroidManifest.xml | 3 +- .../android/dialer/backup/DialerBackupAgent.java | 18 +- .../android/dialer/backup/DialerBackupUtils.java | 44 ++ .../android/dialer/backup/nano/VoicemailInfo.java | 399 +++++++++++++++++ .../android/dialer/backup/proto/VoicemailInfo.java | 377 ---------------- .../android/dialer/binary/aosp/AndroidManifest.xml | 116 +++++ .../dialer/binary/aosp/AospDialerApplication.java | 30 ++ .../binary/aosp/AospDialerRootComponent.java | 30 ++ .../basecomponent/BaseDialerRootComponent.java | 34 ++ .../dialer/binary/common/DialerApplication.java | 43 ++ .../dialer/blocking/FilteredNumbersUtil.java | 6 +- java/com/android/dialer/buildtype/BuildType.java | 3 +- .../buildtype/dogfood/BuildTypeAccessorImpl.java | 30 -- .../buildtype/release/BuildTypeAccessorImpl.java | 30 ++ .../dialer/callcomposer/AndroidManifest.xml | 5 +- .../dialer/callcomposer/CallComposerActivity.java | 346 +++++++-------- .../dialer/callcomposer/CallComposerFragment.java | 75 +--- .../callcomposer/CallComposerPagerAdapter.java | 4 +- .../callcomposer/CameraComposerFragment.java | 43 +- .../callcomposer/GalleryComposerFragment.java | 44 +- .../dialer/callcomposer/GalleryGridAdapter.java | 12 + .../dialer/callcomposer/GalleryGridItemData.java | 35 +- .../callcomposer/MessageComposerFragment.java | 8 +- .../callcomposer/camera/ImagePersistTask.java | 4 +- .../cameraui/res/layout/camera_view.xml | 8 + .../res/layout/call_composer_activity.xml | 7 +- .../res/layout/fragment_gallery_composer.xml | 2 +- .../res/layout/fragment_message_composer.xml | 77 ++-- .../callcomposer/res/values-h260dp/values.xml | 19 + .../callcomposer/res/values-h480dp/values.xml | 19 + .../callcomposer/res/values-w360dp/values.xml | 19 + .../callcomposer/res/values-w500dp/values.xml | 19 + .../dialer/callcomposer/res/values/dimens.xml | 4 - .../dialer/callcomposer/res/values/strings.xml | 6 +- .../dialer/callcomposer/res/values/styles.xml | 11 +- .../dialer/callcomposer/res/values/values.xml | 20 + .../android/dialer/calldetails/AndroidManifest.xml | 31 ++ .../dialer/calldetails/CallDetailsActivity.java | 130 ++++++ .../dialer/calldetails/CallDetailsAdapter.java | 97 +++++ .../calldetails/CallDetailsEntryViewHolder.java | 195 +++++++++ .../calldetails/CallDetailsFooterViewHolder.java | 67 +++ .../calldetails/CallDetailsHeaderViewHolder.java | 125 ++++++ .../calldetails/nano/CallDetailsEntries.java | 440 +++++++++++++++++++ .../res/drawable/multimedia_image_background.xml | 20 + .../res/layout/call_details_activity.xml | 37 ++ .../calldetails/res/layout/call_details_entry.xml | 73 ++++ .../calldetails/res/layout/call_details_footer.xml | 43 ++ .../calldetails/res/layout/contact_container.xml | 60 +++ .../calldetails/res/layout/ec_data_container.xml | 42 ++ .../calldetails/res/menu/call_details_menu.xml | 23 + .../dialer/calldetails/res/values/dimens.xml | 40 ++ .../dialer/calldetails/res/values/strings.xml | 42 ++ .../dialer/calldetails/res/values/styles.xml | 48 +++ .../dialer/callintent/nano/CallInitiationType.java | 29 +- .../dialer/calllogutils/AndroidManifest.xml | 16 + .../dialer/calllogutils/CallEntryFormatter.java | 113 +++++ .../dialer/calllogutils/CallTypeHelper.java | 135 ++++++ .../dialer/calllogutils/CallTypeIconsView.java | 244 +++++++++++ .../dialer/calllogutils/PhoneAccountUtils.java | 104 +++++ .../dialer/calllogutils/PhoneCallDetails.java | 206 +++++++++ .../calllogutils/PhoneNumberDisplayUtil.java | 83 ++++ .../dialer/calllogutils/res/values/colors.xml | 24 ++ .../dialer/calllogutils/res/values/dimens.xml | 19 + .../dialer/calllogutils/res/values/strings.xml | 90 ++++ java/com/android/dialer/common/Assert.java | 30 ++ ...Value_FallibleAsyncTask_FallibleTaskResult.java | 79 ---- .../android/dialer/common/FallibleAsyncTask.java | 6 +- .../dialer/common/PerAccountSharedPreferences.java | 146 +++++++ java/com/android/dialer/common/proguard.flags | 4 + .../android/dialer/common/res/values/config.xml | 4 + java/com/android/dialer/constants/Constants.java | 14 +- .../dialer/database/CallLogQueryHandler.java | 22 +- .../dialer/debug/bindings/stub/DebugBindings.java | 27 ++ .../AutoValue_EnrichedCallCapabilities.java | 76 ---- .../AutoValue_OutgoingCallComposerData.java | 127 ------ .../enrichedcall/EnrichedCallCapabilities.java | 13 +- .../dialer/enrichedcall/EnrichedCallComponent.java | 48 +++ .../dialer/enrichedcall/EnrichedCallManager.java | 131 ++++-- .../enrichedcall/EnrichedCallManagerStub.java | 84 ---- .../enrichedcall/FuzzyPhoneNumberMatcher.java | 20 + .../enrichedcall/OutgoingCallComposerData.java | 16 +- java/com/android/dialer/enrichedcall/Session.java | 7 + .../enrichedcall/StubEnrichedCallModule.java | 32 -- .../dialer/enrichedcall/VideoShareSession.java | 20 + .../enrichedcall/historyquery/HistoryQuery.java | 31 ++ .../historyquery/nano/HistoryResult.java | 203 +++++++++ .../enrichedcall/stub/EnrichedCallManagerStub.java | 145 +++++++ .../enrichedcall/stub/StubEnrichedCallModule.java | 35 ++ .../videoshare/VideoShareListener.java | 14 + .../android/dialer/inject/ApplicationModule.java | 39 -- java/com/android/dialer/inject/ContextModule.java | 39 ++ .../android/dialer/inject/DialerAppComponent.java | 29 -- .../android/dialer/inject/HasRootComponent.java | 25 ++ .../interactions/PhoneNumberInteraction.java | 32 +- .../dialer/logging/nano/ContactLookupResult.java | 29 +- .../android/dialer/logging/nano/ContactSource.java | 29 +- .../dialer/logging/nano/DialerImpression.java | 69 ++- .../dialer/logging/nano/InteractionEvent.java | 2 +- .../dialer/logging/nano/ReportingLocation.java | 29 +- .../android/dialer/logging/nano/ScreenEvent.java | 2 +- .../multimedia/AutoValue_MultimediaData.java | 165 ------- .../android/dialer/multimedia/MultimediaData.java | 33 +- .../dialer/notification/AndroidManifest.xml | 21 + .../notification/GroupedNotificationUtil.java | 66 +++ .../notification/NotificationChannelManager.java | 232 ++++++++++ .../android/dialer/notification/res/values/ids.xml | 27 ++ .../dialer/notification/res/values/strings.xml | 25 ++ java/com/android/dialer/oem/AndroidManifest.xml | 3 + .../dialer/oem/MotorolaHiddenMenuKeySequence.java | 153 +++++++ java/com/android/dialer/oem/MotorolaUtils.java | 51 +++ .../dialer/oem/res/values/motorola_config.xml | 64 +++ .../android/dialer/p13n/inference/P13nRanking.java | 49 ++- .../dialer/p13n/inference/protocol/P13nRanker.java | 10 +- .../CachedNumberLookupService.java | 4 + .../android/dialer/postcall/AndroidManifest.xml | 28 ++ java/com/android/dialer/postcall/PostCall.java | 182 ++++++++ .../android/dialer/postcall/PostCallActivity.java | 151 +++++++ .../postcall/res/layout/post_call_activity.xml | 38 ++ .../android/dialer/postcall/res/values/strings.xml | 31 ++ .../android/dialer/postcall/res/values/values.xml | 19 + java/com/android/dialer/proguard/proguard.flags | 6 + .../android/dialer/proguard/proguard_base.flags | 74 ++++ .../android/dialer/proguard/proguard_release.flags | 24 ++ .../dialer/shortcuts/AutoValue_DialerShortcut.java | 161 ------- .../dialer/shortcuts/CallContactActivity.java | 16 +- .../android/dialer/shortcuts/DialerShortcut.java | 6 +- .../dialer/shortcuts/res/values/strings.xml | 6 +- .../android/dialer/shortcuts/res/xml/shortcuts.xml | 4 +- .../dialer/simulator/SimulatorComponent.java | 46 ++ .../impl/AutoValue_SimulatorCallLog_CallEntry.java | 160 ------- .../impl/AutoValue_SimulatorContacts_Contact.java | 231 ---------- .../AutoValue_SimulatorVoicemail_Voicemail.java | 184 -------- .../dialer/simulator/impl/SimulatorCallLog.java | 6 +- .../dialer/simulator/impl/SimulatorContacts.java | 6 +- .../dialer/simulator/impl/SimulatorImpl.java | 40 ++ .../dialer/simulator/impl/SimulatorModule.java | 22 +- .../dialer/simulator/impl/SimulatorVoicemail.java | 6 +- java/com/android/dialer/telecom/TelecomUtil.java | 33 +- .../theme/res/drawable-hdpi/ic_block_24dp.png | Bin 0 -> 478 bytes .../theme/res/drawable-hdpi/ic_call_arrow.png | Bin 0 -> 538 bytes .../theme/res/drawable-mdpi/ic_block_24dp.png | Bin 0 -> 335 bytes .../theme/res/drawable-mdpi/ic_call_arrow.png | Bin 0 -> 455 bytes .../theme/res/drawable-xhdpi/ic_block_24dp.png | Bin 0 -> 665 bytes .../theme/res/drawable-xhdpi/ic_call_arrow.png | Bin 0 -> 627 bytes .../theme/res/drawable-xxhdpi/ic_block_24dp.png | Bin 0 -> 973 bytes .../theme/res/drawable-xxhdpi/ic_call_arrow.png | Bin 0 -> 1203 bytes .../theme/res/drawable-xxxhdpi/ic_block_24dp.png | Bin 0 -> 1295 bytes .../theme/res/drawable-xxxhdpi/ic_call_arrow.png | Bin 0 -> 1344 bytes .../com/android/dialer/theme/res/values/dimens.xml | 5 + .../com/android/dialer/theme/res/values/styles.xml | 11 + java/com/android/dialer/util/AndroidManifest.xml | 16 + java/com/android/dialer/util/PermissionsUtil.java | 4 + java/com/android/dialer/util/SettingsUtil.java | 9 + java/com/android/dialer/util/ViewUtil.java | 13 + .../com/android/dialer/widget/MessageFragment.java | 172 ++++++++ .../dialer/widget/res/color/dialer_tint_state.xml | 23 + .../dialer/widget/res/layout/fragment_message.xml | 81 ++++ .../widget/res/layout/selectable_text_view.xml | 25 ++ .../android/dialer/widget/res/values/dimens.xml | 23 + .../android/dialer/widget/res/values/strings.xml | 5 + 229 files changed, 8391 insertions(+), 5263 deletions(-) delete mode 100644 java/com/android/dialer/app/CallDetailActivity.java delete mode 100644 java/com/android/dialer/app/DialerApplication.java delete mode 100644 java/com/android/dialer/app/PhoneCallDetails.java delete mode 100644 java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java create mode 100644 java/com/android/dialer/app/calllog/CallLogActivity.java delete mode 100644 java/com/android/dialer/app/calllog/CallLogNotificationsHelper.java create mode 100644 java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java delete mode 100644 java/com/android/dialer/app/calllog/CallTypeHelper.java delete mode 100644 java/com/android/dialer/app/calllog/CallTypeIconsView.java create mode 100644 java/com/android/dialer/app/calllog/PhoneAccountHandles.java delete mode 100644 java/com/android/dialer/app/calllog/PhoneAccountUtils.java delete mode 100644 java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java delete mode 100644 java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png delete mode 100644 java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png delete mode 100644 java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png delete mode 100644 java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png delete mode 100644 java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png create mode 100644 java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png delete mode 100644 java/com/android/dialer/app/res/layout/call_detail.xml delete mode 100644 java/com/android/dialer/app/res/layout/call_detail_footer.xml delete mode 100644 java/com/android/dialer/app/res/layout/call_detail_header.xml create mode 100644 java/com/android/dialer/app/res/layout/call_log_activity.xml create mode 100644 java/com/android/dialer/app/res/menu/call_log_options.xml create mode 100644 java/com/android/dialer/backup/nano/VoicemailInfo.java delete mode 100644 java/com/android/dialer/backup/proto/VoicemailInfo.java create mode 100644 java/com/android/dialer/binary/aosp/AndroidManifest.xml create mode 100644 java/com/android/dialer/binary/aosp/AospDialerApplication.java create mode 100644 java/com/android/dialer/binary/aosp/AospDialerRootComponent.java create mode 100644 java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java create mode 100644 java/com/android/dialer/binary/common/DialerApplication.java delete mode 100644 java/com/android/dialer/buildtype/dogfood/BuildTypeAccessorImpl.java create mode 100644 java/com/android/dialer/buildtype/release/BuildTypeAccessorImpl.java create mode 100644 java/com/android/dialer/callcomposer/res/values-h260dp/values.xml create mode 100644 java/com/android/dialer/callcomposer/res/values-h480dp/values.xml create mode 100644 java/com/android/dialer/callcomposer/res/values-w360dp/values.xml create mode 100644 java/com/android/dialer/callcomposer/res/values-w500dp/values.xml create mode 100644 java/com/android/dialer/callcomposer/res/values/values.xml create mode 100644 java/com/android/dialer/calldetails/AndroidManifest.xml create mode 100644 java/com/android/dialer/calldetails/CallDetailsActivity.java create mode 100644 java/com/android/dialer/calldetails/CallDetailsAdapter.java create mode 100644 java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java create mode 100644 java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java create mode 100644 java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java create mode 100644 java/com/android/dialer/calldetails/nano/CallDetailsEntries.java create mode 100644 java/com/android/dialer/calldetails/res/drawable/multimedia_image_background.xml create mode 100644 java/com/android/dialer/calldetails/res/layout/call_details_activity.xml create mode 100644 java/com/android/dialer/calldetails/res/layout/call_details_entry.xml create mode 100644 java/com/android/dialer/calldetails/res/layout/call_details_footer.xml create mode 100644 java/com/android/dialer/calldetails/res/layout/contact_container.xml create mode 100644 java/com/android/dialer/calldetails/res/layout/ec_data_container.xml create mode 100644 java/com/android/dialer/calldetails/res/menu/call_details_menu.xml create mode 100644 java/com/android/dialer/calldetails/res/values/dimens.xml create mode 100644 java/com/android/dialer/calldetails/res/values/strings.xml create mode 100644 java/com/android/dialer/calldetails/res/values/styles.xml create mode 100644 java/com/android/dialer/calllogutils/AndroidManifest.xml create mode 100644 java/com/android/dialer/calllogutils/CallEntryFormatter.java create mode 100644 java/com/android/dialer/calllogutils/CallTypeHelper.java create mode 100644 java/com/android/dialer/calllogutils/CallTypeIconsView.java create mode 100644 java/com/android/dialer/calllogutils/PhoneAccountUtils.java create mode 100644 java/com/android/dialer/calllogutils/PhoneCallDetails.java create mode 100644 java/com/android/dialer/calllogutils/PhoneNumberDisplayUtil.java create mode 100644 java/com/android/dialer/calllogutils/res/values/colors.xml create mode 100644 java/com/android/dialer/calllogutils/res/values/dimens.xml create mode 100644 java/com/android/dialer/calllogutils/res/values/strings.xml delete mode 100644 java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java create mode 100644 java/com/android/dialer/common/PerAccountSharedPreferences.java create mode 100644 java/com/android/dialer/common/proguard.flags create mode 100644 java/com/android/dialer/common/res/values/config.xml create mode 100644 java/com/android/dialer/debug/bindings/stub/DebugBindings.java delete mode 100644 java/com/android/dialer/enrichedcall/AutoValue_EnrichedCallCapabilities.java delete mode 100644 java/com/android/dialer/enrichedcall/AutoValue_OutgoingCallComposerData.java create mode 100644 java/com/android/dialer/enrichedcall/EnrichedCallComponent.java delete mode 100644 java/com/android/dialer/enrichedcall/EnrichedCallManagerStub.java create mode 100644 java/com/android/dialer/enrichedcall/FuzzyPhoneNumberMatcher.java delete mode 100644 java/com/android/dialer/enrichedcall/StubEnrichedCallModule.java create mode 100644 java/com/android/dialer/enrichedcall/VideoShareSession.java create mode 100644 java/com/android/dialer/enrichedcall/historyquery/HistoryQuery.java create mode 100644 java/com/android/dialer/enrichedcall/historyquery/nano/HistoryResult.java create mode 100644 java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java create mode 100644 java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java create mode 100644 java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java delete mode 100644 java/com/android/dialer/inject/ApplicationModule.java create mode 100644 java/com/android/dialer/inject/ContextModule.java delete mode 100644 java/com/android/dialer/inject/DialerAppComponent.java create mode 100644 java/com/android/dialer/inject/HasRootComponent.java delete mode 100644 java/com/android/dialer/multimedia/AutoValue_MultimediaData.java create mode 100644 java/com/android/dialer/notification/AndroidManifest.xml create mode 100644 java/com/android/dialer/notification/GroupedNotificationUtil.java create mode 100644 java/com/android/dialer/notification/NotificationChannelManager.java create mode 100644 java/com/android/dialer/notification/res/values/ids.xml create mode 100644 java/com/android/dialer/notification/res/values/strings.xml create mode 100644 java/com/android/dialer/oem/AndroidManifest.xml create mode 100644 java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java create mode 100644 java/com/android/dialer/oem/MotorolaUtils.java create mode 100644 java/com/android/dialer/oem/res/values/motorola_config.xml create mode 100644 java/com/android/dialer/postcall/AndroidManifest.xml create mode 100644 java/com/android/dialer/postcall/PostCall.java create mode 100644 java/com/android/dialer/postcall/PostCallActivity.java create mode 100644 java/com/android/dialer/postcall/res/layout/post_call_activity.xml create mode 100644 java/com/android/dialer/postcall/res/values/strings.xml create mode 100644 java/com/android/dialer/postcall/res/values/values.xml create mode 100644 java/com/android/dialer/proguard/proguard.flags create mode 100644 java/com/android/dialer/proguard/proguard_base.flags create mode 100644 java/com/android/dialer/proguard/proguard_release.flags delete mode 100644 java/com/android/dialer/shortcuts/AutoValue_DialerShortcut.java create mode 100644 java/com/android/dialer/simulator/SimulatorComponent.java delete mode 100644 java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java delete mode 100644 java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java delete mode 100644 java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java create mode 100644 java/com/android/dialer/simulator/impl/SimulatorImpl.java create mode 100644 java/com/android/dialer/theme/res/drawable-hdpi/ic_block_24dp.png create mode 100644 java/com/android/dialer/theme/res/drawable-hdpi/ic_call_arrow.png create mode 100644 java/com/android/dialer/theme/res/drawable-mdpi/ic_block_24dp.png create mode 100644 java/com/android/dialer/theme/res/drawable-mdpi/ic_call_arrow.png create mode 100644 java/com/android/dialer/theme/res/drawable-xhdpi/ic_block_24dp.png create mode 100644 java/com/android/dialer/theme/res/drawable-xhdpi/ic_call_arrow.png create mode 100644 java/com/android/dialer/theme/res/drawable-xxhdpi/ic_block_24dp.png create mode 100644 java/com/android/dialer/theme/res/drawable-xxhdpi/ic_call_arrow.png create mode 100644 java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_block_24dp.png create mode 100644 java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_call_arrow.png create mode 100644 java/com/android/dialer/widget/MessageFragment.java create mode 100644 java/com/android/dialer/widget/res/color/dialer_tint_state.xml create mode 100644 java/com/android/dialer/widget/res/layout/fragment_message.xml create mode 100644 java/com/android/dialer/widget/res/layout/selectable_text_view.xml create mode 100644 java/com/android/dialer/widget/res/values/dimens.xml create mode 100644 java/com/android/dialer/widget/res/values/strings.xml (limited to 'java/com/android/dialer') diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml index 80f294acc..5ce13dbd7 100644 --- a/java/com/android/dialer/app/AndroidManifest.xml +++ b/java/com/android/dialer/app/AndroidManifest.xml @@ -57,11 +57,7 @@ android:minSdkVersion="23" android:targetSdkVersion="25"/> - + + + + diff --git a/java/com/android/dialer/app/CallDetailActivity.java b/java/com/android/dialer/app/CallDetailActivity.java deleted file mode 100644 index cda2b2e2c..000000000 --- a/java/com/android/dialer/app/CallDetailActivity.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2009 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; - -import android.content.ContentUris; -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.ContactsContract.CommonDataKinds.Phone; -import android.support.v7.app.AppCompatActivity; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ListView; -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.ContactPhotoManager.DefaultImageRequest; -import com.android.contacts.common.GeoUtil; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.UriUtils; -import com.android.dialer.app.calllog.CallDetailHistoryAdapter; -import com.android.dialer.app.calllog.CallLogAsyncTaskUtil; -import com.android.dialer.app.calllog.CallLogAsyncTaskUtil.CallLogAsyncTaskListener; -import com.android.dialer.app.calllog.CallTypeHelper; -import com.android.dialer.app.calllog.PhoneAccountUtils; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.callintent.CallIntentBuilder; -import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.common.Assert; -import com.android.dialer.common.AsyncTaskExecutor; -import com.android.dialer.common.AsyncTaskExecutors; -import com.android.dialer.compat.CompatUtils; -import com.android.dialer.logging.Logger; -import com.android.dialer.logging.nano.DialerImpression; -import com.android.dialer.logging.nano.ScreenEvent; -import com.android.dialer.phonenumbercache.ContactInfoHelper; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import com.android.dialer.proguard.UsedByReflection; -import com.android.dialer.spam.Spam; -import com.android.dialer.telecom.TelecomUtil; -import com.android.dialer.util.CallUtil; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.TouchPointManager; - -/** - * Displays the details of a specific call log entry. - * - *

This activity can be either started with the URI of a single call log entry, or with the - * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries. - */ -@UsedByReflection(value = "AndroidManifest-app.xml") -public class CallDetailActivity extends AppCompatActivity - implements MenuItem.OnMenuItemClickListener, View.OnClickListener { - - /** A long array extra containing ids of call log entries to display. */ - public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS"; - /** If we are started with a voicemail, we'll find the uri to play with this extra. */ - public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI"; - /** If the activity was triggered from a notification. */ - public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION"; - - public static final String BLOCKED_OR_SPAM_QUERY_IDENTIFIER = "blockedOrSpamIdentifier"; - - private final AsyncTaskExecutor executor = AsyncTaskExecutors.createAsyncTaskExecutor(); - protected String mNumber; - private Context mContext; - private ContactInfoHelper mContactInfoHelper; - private ContactsPreferences mContactsPreferences; - private CallTypeHelper mCallTypeHelper; - private ContactPhotoManager mContactPhotoManager; - private BidiFormatter mBidiFormatter = BidiFormatter.getInstance(); - private LayoutInflater mInflater; - private Resources mResources; - private PhoneCallDetails mDetails; - private Uri mVoicemailUri; - private String mPostDialDigits = ""; - private ListView mHistoryList; - private QuickContactBadge mQuickContactBadge; - private TextView mCallerName; - private TextView mCallerNumber; - private TextView mAccountLabel; - private View mCallButton; - private View mEditBeforeCallActionItem; - private View mReportActionItem; - private View mCopyNumberActionItem; - private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; - private CallLogAsyncTaskListener mCallLogAsyncTaskListener = - new CallLogAsyncTaskListener() { - @Override - public void onDeleteCall() { - finish(); - } - - @Override - public void onDeleteVoicemail() { - finish(); - } - - @Override - public void onGetCallDetails(final PhoneCallDetails[] details) { - if (details == null) { - // Somewhere went wrong: we're going to bail out and show error to users. - Toast.makeText(mContext, R.string.toast_call_detail_error, Toast.LENGTH_SHORT).show(); - finish(); - return; - } - - // All calls are from the same number and same contact, so pick the first detail. - mDetails = details[0]; - mNumber = TextUtils.isEmpty(mDetails.number) ? null : mDetails.number.toString(); - - if (mNumber == null) { - updateDataAndRender(details); - return; - } - - executor.submit( - BLOCKED_OR_SPAM_QUERY_IDENTIFIER, - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - mDetails.isBlocked = - mFilteredNumberAsyncQueryHandler.getBlockedIdSynchronousForCalllogOnly( - mNumber, mDetails.countryIso) - != null; - if (Spam.get(mContext).isSpamEnabled()) { - mDetails.isSpam = - hasIncomingCalls(details) - && Spam.get(mContext) - .checkSpamStatusSynchronous(mNumber, mDetails.countryIso); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - updateDataAndRender(details); - } - }); - } - - private void updateDataAndRender(PhoneCallDetails[] details) { - mPostDialDigits = - TextUtils.isEmpty(mDetails.postDialDigits) ? "" : mDetails.postDialDigits; - - final CharSequence callLocationOrType = getNumberTypeOrLocation(mDetails); - - final CharSequence displayNumber; - if (!TextUtils.isEmpty(mDetails.postDialDigits)) { - displayNumber = mDetails.number + mDetails.postDialDigits; - } else { - displayNumber = mDetails.displayNumber; - } - - final String displayNumberStr = - mBidiFormatter.unicodeWrap(displayNumber.toString(), TextDirectionHeuristics.LTR); - - mDetails.nameDisplayOrder = mContactsPreferences.getDisplayOrder(); - - if (!TextUtils.isEmpty(mDetails.getPreferredName())) { - mCallerName.setText(mDetails.getPreferredName()); - mCallerNumber.setText(callLocationOrType + " " + displayNumberStr); - } else { - mCallerName.setText(displayNumberStr); - if (!TextUtils.isEmpty(callLocationOrType)) { - mCallerNumber.setText(callLocationOrType); - mCallerNumber.setVisibility(View.VISIBLE); - } else { - mCallerNumber.setVisibility(View.GONE); - } - } - - CharSequence accountLabel = - PhoneAccountUtils.getAccountLabel(mContext, mDetails.accountHandle); - CharSequence accountContentDescription = - PhoneCallDetails.createAccountLabelDescription( - mResources, mDetails.viaNumber, accountLabel); - if (!TextUtils.isEmpty(mDetails.viaNumber)) { - if (!TextUtils.isEmpty(accountLabel)) { - accountLabel = - mResources.getString( - R.string.call_log_via_number_phone_account, accountLabel, mDetails.viaNumber); - } else { - accountLabel = mResources.getString(R.string.call_log_via_number, mDetails.viaNumber); - } - } - if (!TextUtils.isEmpty(accountLabel)) { - mAccountLabel.setText(accountLabel); - mAccountLabel.setContentDescription(accountContentDescription); - mAccountLabel.setVisibility(View.VISIBLE); - } else { - mAccountLabel.setVisibility(View.GONE); - } - - final boolean canPlaceCallsTo = - PhoneNumberHelper.canPlaceCallsTo(mNumber, mDetails.numberPresentation); - mCallButton.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - mCopyNumberActionItem.setVisibility(canPlaceCallsTo ? View.VISIBLE : View.GONE); - - final boolean isSipNumber = PhoneNumberHelper.isSipNumber(mNumber); - final boolean isVoicemailNumber = - PhoneNumberHelper.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); - final boolean showEditNumberBeforeCallAction = - canPlaceCallsTo && !isSipNumber && !isVoicemailNumber; - mEditBeforeCallActionItem.setVisibility( - showEditNumberBeforeCallAction ? View.VISIBLE : View.GONE); - - final boolean showReportAction = - mContactInfoHelper.canReportAsInvalid(mDetails.sourceType, mDetails.objectId); - mReportActionItem.setVisibility(showReportAction ? View.VISIBLE : View.GONE); - - invalidateOptionsMenu(); - - mHistoryList.setAdapter( - new CallDetailHistoryAdapter(mContext, mInflater, mCallTypeHelper, details)); - - updateContactPhoto(mDetails.isSpam); - - findViewById(R.id.call_detail).setVisibility(View.VISIBLE); - } - - /** - * Determines the location geocode text for a call, or the phone number type (if available). - * - * @param details The call details. - * @return The phone number type or location. - */ - private CharSequence getNumberTypeOrLocation(PhoneCallDetails details) { - if (details.isSpam) { - return mResources.getString(R.string.spam_number_call_log_label); - } else if (details.isBlocked) { - return mResources.getString(R.string.blocked_number_call_log_label); - } else if (!TextUtils.isEmpty(details.namePrimary)) { - return Phone.getTypeLabel(mResources, details.numberType, details.numberLabel); - } else { - return details.geocode; - } - } - }; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - mContext = this; - mResources = getResources(); - mContactInfoHelper = new ContactInfoHelper(this, GeoUtil.getCurrentCountryIso(this)); - mContactsPreferences = new ContactsPreferences(mContext); - mCallTypeHelper = new CallTypeHelper(getResources()); - mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(mContext); - - mVoicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - setContentView(R.layout.call_detail); - mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); - - mHistoryList = (ListView) findViewById(R.id.history); - mHistoryList.addHeaderView(mInflater.inflate(R.layout.call_detail_header, null)); - mHistoryList.addFooterView(mInflater.inflate(R.layout.call_detail_footer, null), null, false); - - mQuickContactBadge = (QuickContactBadge) findViewById(R.id.quick_contact_photo); - mQuickContactBadge.setOverlay(null); - if (CompatUtils.hasPrioritizedMimeType()) { - mQuickContactBadge.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); - } - mCallerName = (TextView) findViewById(R.id.caller_name); - mCallerNumber = (TextView) findViewById(R.id.caller_number); - mAccountLabel = (TextView) findViewById(R.id.phone_account_label); - mContactPhotoManager = ContactPhotoManager.getInstance(this); - - mCallButton = findViewById(R.id.call_back_button); - mCallButton.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - if (TextUtils.isEmpty(mNumber)) { - return; - } - DialerUtils.startActivityWithErrorToast( - CallDetailActivity.this, - new CallIntentBuilder(getDialableNumber(), CallInitiationType.Type.CALL_DETAILS) - .build()); - } - }); - - mEditBeforeCallActionItem = findViewById(R.id.call_detail_action_edit_before_call); - mEditBeforeCallActionItem.setOnClickListener(this); - mReportActionItem = findViewById(R.id.call_detail_action_report); - mReportActionItem.setOnClickListener(this); - - mCopyNumberActionItem = findViewById(R.id.call_detail_action_copy); - mCopyNumberActionItem.setOnClickListener(this); - - if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) { - closeSystemDialogs(); - } - - Logger.get(this).logScreenView(ScreenEvent.Type.CALL_DETAILS, this); - } - - @Override - public void onResume() { - super.onResume(); - mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); - getCallDetails(); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY()); - } - return super.dispatchTouchEvent(ev); - } - - public void getCallDetails() { - CallLogAsyncTaskUtil.getCallDetails(this, mCallLogAsyncTaskListener, getCallLogEntryUris()); - } - - /** - * Returns the list of URIs to show. - * - *

There are two ways the URIs can be provided to the activity: as the data on the intent, or - * as a list of ids in the call log added as an extra on the URI. - * - *

If both are available, the data on the intent takes precedence. - */ - private Uri[] getCallLogEntryUris() { - final Uri uri = getIntent().getData(); - if (uri != null) { - // If there is a data on the intent, it takes precedence over the extra. - return new Uri[] {uri}; - } - final long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS); - final int numIds = ids == null ? 0 : ids.length; - final Uri[] uris = new Uri[numIds]; - for (int index = 0; index < numIds; ++index) { - uris[index] = - ContentUris.withAppendedId( - TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]); - } - return uris; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - final MenuItem deleteMenuItem = - menu.add( - Menu.NONE, R.id.call_detail_delete_menu_item, Menu.NONE, R.string.call_details_delete); - deleteMenuItem.setIcon(R.drawable.ic_delete_24dp); - deleteMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - deleteMenuItem.setOnMenuItemClickListener(this); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.call_detail_delete_menu_item) { - Logger.get(mContext).logImpression(DialerImpression.Type.USER_DELETED_CALL_LOG_ITEM); - if (hasVoicemail()) { - CallLogAsyncTaskUtil.deleteVoicemail(this, mVoicemailUri, mCallLogAsyncTaskListener); - } else { - final StringBuilder callIds = new StringBuilder(); - for (Uri callUri : getCallLogEntryUris()) { - if (callIds.length() != 0) { - callIds.append(","); - } - callIds.append(ContentUris.parseId(callUri)); - } - CallLogAsyncTaskUtil.deleteCalls(this, callIds.toString(), mCallLogAsyncTaskListener); - } - } - return true; - } - - @Override - public void onClick(View view) { - int resId = view.getId(); - if (resId == R.id.call_detail_action_copy) { - ClipboardUtils.copyText(mContext, null, mNumber, true); - } else if (resId == R.id.call_detail_action_edit_before_call) { - Intent dialIntent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(getDialableNumber())); - DialerUtils.startActivityWithErrorToast(mContext, dialIntent); - } else { - Assert.fail("Unexpected onClick event from " + view); - } - } - - // Loads and displays the contact photo. - private void updateContactPhoto(boolean isSpam) { - if (mDetails == null) { - return; - } - - mQuickContactBadge.assignContactUri(mDetails.contactUri); - final String displayName = - TextUtils.isEmpty(mDetails.namePrimary) - ? mDetails.displayNumber - : mDetails.namePrimary.toString(); - mQuickContactBadge.setContentDescription( - mResources.getString(R.string.description_contact_details, displayName)); - - final boolean isVoicemailNumber = - PhoneNumberHelper.isVoicemailNumber(mContext, mDetails.accountHandle, mNumber); - if (isSpam) { - mQuickContactBadge.setImageDrawable(mContext.getDrawable(R.drawable.blocked_contact)); - return; - } - - final boolean isBusiness = mContactInfoHelper.isBusiness(mDetails.sourceType); - int contactType = ContactPhotoManager.TYPE_DEFAULT; - if (isVoicemailNumber) { - contactType = ContactPhotoManager.TYPE_VOICEMAIL; - } else if (isBusiness) { - contactType = ContactPhotoManager.TYPE_BUSINESS; - } - - final String lookupKey = - mDetails.contactUri == null ? null : UriUtils.getLookupKeyFromUri(mDetails.contactUri); - - final DefaultImageRequest request = - new DefaultImageRequest(displayName, lookupKey, contactType, true /* isCircular */); - - mContactPhotoManager.loadDirectoryPhoto( - mQuickContactBadge, - mDetails.photoUri, - false /* darkTheme */, - true /* isCircular */, - request); - } - - private void closeSystemDialogs() { - sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - private String getDialableNumber() { - return mNumber + mPostDialDigits; - } - - public boolean hasVoicemail() { - return mVoicemailUri != null; - } - - private static boolean hasIncomingCalls(PhoneCallDetails[] details) { - for (int i = 0; i < details.length; i++) { - if (details[i].hasIncomingCalls()) { - return true; - } - } - return false; - } -} diff --git a/java/com/android/dialer/app/DialerApplication.java b/java/com/android/dialer/app/DialerApplication.java deleted file mode 100644 index 3b979212b..000000000 --- a/java/com/android/dialer/app/DialerApplication.java +++ /dev/null @@ -1,77 +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; - -import android.app.Application; -import android.os.Trace; -import android.preference.PreferenceManager; -import com.android.dialer.blocking.BlockedNumbersAutoMigrator; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.enrichedcall.EnrichedCallManager; -import com.android.dialer.inject.ApplicationModule; -import com.android.dialer.inject.DaggerDialerAppComponent; -import com.android.dialer.inject.DialerAppComponent; - -public class DialerApplication extends Application implements EnrichedCallManager.Factory { - - private static final String TAG = "DialerApplication"; - - private volatile DialerAppComponent component; - - @Override - public void onCreate() { - Trace.beginSection(TAG + " onCreate"); - super.onCreate(); - new BlockedNumbersAutoMigrator( - this, - PreferenceManager.getDefaultSharedPreferences(this), - new FilteredNumberAsyncQueryHandler(this)) - .autoMigrate(); - Trace.endSection(); - } - - @Override - public EnrichedCallManager getEnrichedCallManager() { - return component().enrichedCallManager(); - } - - protected DialerAppComponent buildApplicationComponent() { - return DaggerDialerAppComponent.builder() - .applicationModule(new ApplicationModule(this)) - .build(); - } - - /** - * Returns the application component. - * - *

A single Component is created per application instance. Note that it won't be instantiated - * until it's first requested, but guarantees that only one will ever be created. - */ - private final DialerAppComponent component() { - // Double-check idiom for lazy initialization - DialerAppComponent result = component; - if (result == null) { - synchronized (this) { - result = component; - if (result == null) { - component = result = buildApplicationComponent(); - } - } - } - return result; - } -} diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 4c57cda70..b2837769f 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -63,15 +63,14 @@ import android.widget.Toast; import com.android.contacts.common.dialog.ClearFrequentsDialog; import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; import com.android.contacts.common.list.PhoneNumberListAdapter; -import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery; import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker; import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener; import com.android.contacts.common.widget.FloatingActionButtonController; import com.android.dialer.animation.AnimUtils; import com.android.dialer.animation.AnimationListenerAdapter; +import com.android.dialer.app.calllog.CallLogActivity; import com.android.dialer.app.calllog.CallLogFragment; import com.android.dialer.app.calllog.CallLogNotificationsService; -import com.android.dialer.app.calllog.ClearCallLogDialog; import com.android.dialer.app.dialpad.DialpadFragment; import com.android.dialer.app.list.DragDropController; import com.android.dialer.app.list.ListsFragment; @@ -85,6 +84,7 @@ import com.android.dialer.app.list.SpeedDialFragment; import com.android.dialer.app.settings.DialerSettingsActivity; import com.android.dialer.app.widget.ActionBarController; import com.android.dialer.app.widget.SearchEditTextLayout; +import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallSpecificAppData; import com.android.dialer.common.Assert; @@ -101,7 +101,10 @@ import com.android.dialer.p13n.inference.protocol.P13nRanker; import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener; import com.android.dialer.p13n.logging.P13nLogger; import com.android.dialer.p13n.logging.P13nLogging; +import com.android.dialer.postcall.PostCall; import com.android.dialer.proguard.UsedByReflection; +import com.android.dialer.simulator.Simulator; +import com.android.dialer.simulator.SimulatorComponent; import com.android.dialer.smartdial.SmartDialNameMatcher; import com.android.dialer.smartdial.SmartDialPrefix; import com.android.dialer.telecom.TelecomUtil; @@ -124,7 +127,6 @@ public class DialtactsActivity extends TransactionSafeActivity OnListFragmentScrolledListener, CallLogFragment.HostInterface, DialpadFragment.HostInterface, - ListsFragment.HostInterface, SpeedDialFragment.HostInterface, SearchFragment.HostInterface, OnDragDropListener, @@ -478,6 +480,7 @@ public class DialtactsActivity extends TransactionSafeActivity @Override protected void onResume() { + LogUtil.d("DialtactsActivity.onResume", ""); Trace.beginSection(TAG + " onResume"); super.onResume(); @@ -490,6 +493,8 @@ public class DialtactsActivity extends TransactionSafeActivity } else if (mShowDialpadOnResume) { showDialpadFragment(false); mShowDialpadOnResume = false; + } else { + PostCall.promptUserForMessageIfNecessary(this, mParentLayout); } // If there was a voice query result returned in the {@link #onActivityResult} callback, it @@ -539,7 +544,7 @@ public class DialtactsActivity extends TransactionSafeActivity } if (getIntent().getBooleanExtra(EXTRA_CLEAR_NEW_VOICEMAILS, false)) { - CallLogNotificationsService.markNewVoicemailsAsOld(this); + CallLogNotificationsService.markNewVoicemailsAsOld(this, null); } setSearchBoxHint(); @@ -588,6 +593,7 @@ public class DialtactsActivity extends TransactionSafeActivity @Override public void onAttachFragment(final Fragment fragment) { + LogUtil.d("DialtactsActivity.onAttachFragment", "fragment: %s", fragment); if (fragment instanceof DialpadFragment) { mDialpadFragment = (DialpadFragment) fragment; if (!mIsDialpadShown && !mShowDialpadOnResume) { @@ -616,7 +622,8 @@ public class DialtactsActivity extends TransactionSafeActivity @MainThread public Cursor rerankCursor(Cursor data) { Assert.isMainThread(); - return mP13nRanker.rankCursor(data, PhoneQuery.PHONE_NUMBER); + String queryString = searchFragment.getQueryString(); + return mP13nRanker.rankCursor(data, queryString == null ? 0 : queryString.length()); } }); searchFragment.addOnLoadFinishedListener( @@ -674,9 +681,9 @@ public class DialtactsActivity extends TransactionSafeActivity } int resId = item.getItemId(); - if (item.getItemId() == R.id.menu_delete_all) { - ClearCallLogDialog.show(getFragmentManager()); - return true; + if (resId == R.id.menu_history) { + final Intent intent = new Intent(this, CallLogActivity.class); + startActivity(intent); } else if (resId == R.id.menu_clear_frequents) { ClearFrequentsDialog.show(getFragmentManager()); Logger.get(this).logScreenView(ScreenEvent.Type.CLEAR_FREQUENTS, this); @@ -691,6 +698,11 @@ public class DialtactsActivity extends TransactionSafeActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + LogUtil.i( + "DialtactsActivity.onActivityResult", + "requestCode:%d, resultCode:%d", + requestCode, + resultCode); if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) { if (resultCode == RESULT_OK) { final ArrayList matches = @@ -701,15 +713,16 @@ public class DialtactsActivity extends TransactionSafeActivity LogUtil.i("DialtactsActivity.onActivityResult", "voice search - nothing heard"); } } else { - LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed: " + resultCode); + LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed"); } } else if (requestCode == ACTIVITY_REQUEST_CODE_CALL_COMPOSE) { - if (resultCode != RESULT_OK) { + if (resultCode == RESULT_FIRST_USER) { LogUtil.i( - "DialtactsActivity.onActivityResult", - "returned from call composer, error occurred (resultCode=" + resultCode + ")"); + "DialtactsActivity.onActivityResult", "returned from call composer, error occurred"); String message = - getString(R.string.call_composer_connection_failed, getString(R.string.share_and_call)); + getString( + R.string.call_composer_connection_failed, + data.getStringExtra(CallComposerActivity.KEY_CONTACT_NAME)); Snackbar.make(mParentLayout, message, Snackbar.LENGTH_LONG).show(); } else { LogUtil.i("DialtactsActivity.onActivityResult", "returned from call composer, no error"); @@ -732,6 +745,7 @@ public class DialtactsActivity extends TransactionSafeActivity * @see #onDialpadShown */ private void showDialpadFragment(boolean animate) { + LogUtil.d("DialtactActivity.showDialpadFragment", "animate: %b", animate); if (mIsDialpadShown || mStateSaved) { return; } @@ -767,6 +781,7 @@ public class DialtactsActivity extends TransactionSafeActivity /** Callback from child DialpadFragment when the dialpad is shown. */ public void onDialpadShown() { + LogUtil.d("DialtactsActivity.onDialpadShown", ""); Assert.isNotNull(mDialpadFragment); if (mDialpadFragment.getAnimate()) { Assert.isNotNull(mDialpadFragment.getView()).startAnimation(mSlideIn); @@ -838,12 +853,21 @@ public class DialtactsActivity extends TransactionSafeActivity private void updateSearchFragmentPosition() { SearchFragment fragment = null; - if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) { + if (mSmartDialSearchFragment != null) { fragment = mSmartDialSearchFragment; - } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) { + } else if (mRegularSearchFragment != null) { fragment = mRegularSearchFragment; } - if (fragment != null && fragment.isVisible()) { + LogUtil.d( + "DialtactsActivity.updateSearchFragmentPosition", + "fragment: %s, isVisible: %b", + fragment, + fragment != null && fragment.isVisible()); + if (fragment != null) { + // We need to force animation here even when fragment is not visible since it might not be + // visible immediately after screen orientation change and dialpad height would not be + // available immediately which is required to update position. By forcing an animation, + // position will be updated after a delay by when the dialpad height would be available. fragment.updatePosition(true /* animate */); } } @@ -858,11 +882,6 @@ public class DialtactsActivity extends TransactionSafeActivity return !TextUtils.isEmpty(mSearchQuery); } - @Override - public boolean shouldShowActionBar() { - return mListsFragment.shouldShowActionBar(); - } - private void setNotInSearchUi() { mInDialpadSearch = false; mInRegularSearch = false; @@ -1056,7 +1075,8 @@ public class DialtactsActivity extends TransactionSafeActivity } // DialtactsActivity will provide the options menu fragment.setHasOptionsMenu(false); - fragment.setShowEmptyListForNullQuery(true); + // Will show empty list if P13nRanker is not enabled. Else, re-ranked list by the ranker. + fragment.setShowEmptyListForNullQuery(mP13nRanker.shouldShowEmptyListForNullQuery()); if (!smartDialSearch) { fragment.setQueryString(query); } @@ -1360,11 +1380,6 @@ public class DialtactsActivity extends TransactionSafeActivity return mActionBarController.isActionBarShowing(); } - @Override - public ActionBarController getActionBarController() { - return mActionBarController; - } - @Override public boolean isDialpadShown() { return mIsDialpadShown; @@ -1378,11 +1393,6 @@ public class DialtactsActivity extends TransactionSafeActivity return 0; } - @Override - public int getActionBarHideOffset() { - return getActionBarSafely().getHideOffset(); - } - @Override public void setActionBarHideOffset(int offset) { getActionBarSafely().setHideOffset(offset); @@ -1461,8 +1471,19 @@ public class DialtactsActivity extends TransactionSafeActivity && mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission); - menu.findItem(R.id.menu_delete_all) + menu.findItem(R.id.menu_history) .setVisible(PermissionsUtil.hasPhonePermissions(DialtactsActivity.this)); + + Context context = DialtactsActivity.this.getApplicationContext(); + MenuItem simulatorMenuItem = menu.findItem(R.id.menu_simulator_submenu); + Simulator simulator = SimulatorComponent.get(context).getSimulator(); + if (simulator.shouldShow()) { + simulatorMenuItem.setVisible(true); + simulatorMenuItem.setActionProvider(simulator.getActionProvider(context)); + } else { + simulatorMenuItem.setVisible(false); + } + super.show(); } } diff --git a/java/com/android/dialer/app/PhoneCallDetails.java b/java/com/android/dialer/app/PhoneCallDetails.java deleted file mode 100644 index 436f68eec..000000000 --- a/java/com/android/dialer/app/PhoneCallDetails.java +++ /dev/null @@ -1,207 +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; - -import android.content.Context; -import android.content.res.Resources; -import android.net.Uri; -import android.provider.CallLog; -import android.provider.CallLog.Calls; -import android.support.annotation.Nullable; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; -import com.android.contacts.common.ContactsUtils.UserType; -import com.android.contacts.common.preference.ContactsPreferences; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.app.calllog.PhoneNumberDisplayUtil; -import com.android.dialer.phonenumbercache.ContactInfo; - -/** The details of a phone call to be shown in the UI. */ -public class PhoneCallDetails { - - // The number of the other party involved in the call. - public CharSequence number; - // Post-dial digits associated with the outgoing call. - public String postDialDigits; - // The secondary line number the call was received via. - public String viaNumber; - // The number presenting rules set by the network, e.g., {@link Calls#PRESENTATION_ALLOWED} - public int numberPresentation; - // The country corresponding with the phone number. - public String countryIso; - // The geocoded location for the phone number. - public String geocode; - - /** - * The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}. - * - *

There might be multiple types if this represents a set of entries grouped together. - */ - public int[] callTypes; - - // The date of the call, in milliseconds since the epoch. - public long date; - // The duration of the call in milliseconds, or 0 for missed calls. - public long duration; - // The name of the contact, or the empty string. - public CharSequence namePrimary; - // The alternative name of the contact, e.g. last name first, or the empty string - public CharSequence nameAlternative; - /** - * The user's preference on name display order, last name first or first time first. {@see - * ContactsPreferences} - */ - public int nameDisplayOrder; - // The type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available. - public int numberType; - // The custom label associated with the phone number in the contact, or the empty string. - public CharSequence numberLabel; - // The URI of the contact associated with this phone call. - public Uri contactUri; - - /** - * The photo URI of the picture of the contact that is associated with this phone call or null if - * there is none. - * - *

This is meant to store the high-res photo only. - */ - public Uri photoUri; - - // The source type of the contact associated with this call. - public int sourceType; - - // The object id type of the contact associated with this call. - public String objectId; - - // The unique identifier for the account associated with the call. - public PhoneAccountHandle accountHandle; - - // Features applicable to this call. - public int features; - - // Total data usage for this call. - public Long dataUsage; - - // Voicemail transcription - public String transcription; - - // The display string for the number. - public String displayNumber; - - // Whether the contact number is a voicemail number. - public boolean isVoicemail; - - /** The {@link UserType} of the contact */ - public @UserType long contactUserType; - - /** - * If this is a voicemail, whether the message is read. For other types of calls, this defaults to - * {@code true}. - */ - public boolean isRead = true; - - // If this call is a spam number. - public boolean isSpam = false; - - // If this call is a blocked number. - public boolean isBlocked = false; - - // Call location and date text. - public CharSequence callLocationAndDate; - - // Call description. - public CharSequence callDescription; - public String accountComponentName; - public String accountId; - public ContactInfo cachedContactInfo; - public int voicemailId; - public int previousGroup; - - /** - * Constructor with required fields for the details of a call with a number associated with a - * contact. - */ - public PhoneCallDetails( - CharSequence number, int numberPresentation, CharSequence postDialDigits) { - this.number = number; - this.numberPresentation = numberPresentation; - this.postDialDigits = postDialDigits.toString(); - } - /** - * Construct the "on {accountLabel} via {viaNumber}" accessibility description for the account - * list item, depending on the existence of the accountLabel and viaNumber. - * - * @param viaNumber The number that this call is being placed via. - * @param accountLabel The {@link PhoneAccount} label that this call is being placed with. - * @return The description of the account that this call has been placed on. - */ - public static CharSequence createAccountLabelDescription( - Resources resources, @Nullable String viaNumber, @Nullable CharSequence accountLabel) { - - if ((!TextUtils.isEmpty(viaNumber)) && !TextUtils.isEmpty(accountLabel)) { - String msg = - resources.getString( - R.string.description_via_number_phone_account, accountLabel, viaNumber); - CharSequence accountNumberLabel = - ContactDisplayUtils.getTelephoneTtsSpannable(msg, viaNumber); - return (accountNumberLabel == null) ? msg : accountNumberLabel; - } else if (!TextUtils.isEmpty(viaNumber)) { - CharSequence viaNumberLabel = - ContactDisplayUtils.getTtsSpannedPhoneNumber( - resources, R.string.description_via_number, viaNumber); - return (viaNumberLabel == null) ? viaNumber : viaNumberLabel; - } else if (!TextUtils.isEmpty(accountLabel)) { - return TextUtils.expandTemplate( - resources.getString(R.string.description_phone_account), accountLabel); - } - return ""; - } - - /** - * Returns the preferred name for the call details as specified by the {@link #nameDisplayOrder} - * - * @return the preferred name - */ - public CharSequence getPreferredName() { - if (nameDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY - || TextUtils.isEmpty(nameAlternative)) { - return namePrimary; - } - return nameAlternative; - } - - public void updateDisplayNumber( - Context context, CharSequence formattedNumber, boolean isVoicemail) { - displayNumber = - PhoneNumberDisplayUtil.getDisplayNumber( - context, number, numberPresentation, formattedNumber, postDialDigits, isVoicemail) - .toString(); - } - - public boolean hasIncomingCalls() { - for (int i = 0; i < callTypes.length; i++) { - if (callTypes[i] == CallLog.Calls.INCOMING_TYPE - || callTypes[i] == CallLog.Calls.MISSED_TYPE - || callTypes[i] == CallLog.Calls.VOICEMAIL_TYPE - || callTypes[i] == CallLog.Calls.REJECTED_TYPE - || callTypes[i] == CallLog.Calls.BLOCKED_TYPE) { - return true; - } - } - return false; - } -} diff --git a/java/com/android/dialer/app/SpecialCharSequenceMgr.java b/java/com/android/dialer/app/SpecialCharSequenceMgr.java index 2ae19704a..712659c12 100644 --- a/java/com/android/dialer/app/SpecialCharSequenceMgr.java +++ b/java/com/android/dialer/app/SpecialCharSequenceMgr.java @@ -28,15 +28,14 @@ import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Looper; import android.provider.Settings; import android.support.annotation.Nullable; +import android.support.v4.os.BuildCompat; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.TextUtils; -import android.util.Log; import android.view.WindowManager; import android.widget.EditText; import android.widget.Toast; @@ -46,8 +45,11 @@ import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.contacts.common.util.ContactDisplayUtils; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; -import com.android.dialer.app.calllog.PhoneAccountUtils; +import com.android.dialer.calllogutils.PhoneAccountUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; +import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.telecom.TelecomUtil; import java.util.ArrayList; import java.util.List; @@ -100,12 +102,19 @@ public class SpecialCharSequenceMgr { //get rid of the separators so that the string gets parsed correctly String dialString = PhoneNumberUtils.stripSeparators(input); - return handleDeviceIdDisplay(context, dialString) + if (handleDeviceIdDisplay(context, dialString) || handleRegulatoryInfoDisplay(context, dialString) || handlePinEntry(context, dialString) || handleAdnEntry(context, dialString, textField) - || handleSecretCode(context, dialString); + || handleSecretCode(context, dialString)) { + return true; + } + + if (MotorolaUtils.handleSpecialCharSequence(context, input)) { + return true; + } + return false; } /** @@ -114,10 +123,7 @@ public class SpecialCharSequenceMgr { *

This should be called when the screen becomes background. */ public static void cleanup() { - if (Looper.myLooper() != Looper.getMainLooper()) { - Log.wtf(TAG, "cleanup() is called outside the main thread"); - return; - } + Assert.isMainThread(); if (sPreviousAdnQueryHandler != null) { sPreviousAdnQueryHandler.cancel(); @@ -126,14 +132,21 @@ public class SpecialCharSequenceMgr { } /** - * Handles secret codes to launch arbitrary activities in the form of *#*##*#*. If a secret - * code is encountered an Intent is started with the android_secret_code:// URI. + * Handles secret codes to launch arbitrary activities in the form of *#*##*#*. + * If a secret code is encountered, an Intent is started with the android_secret_code:// + * URI. * * @param context the context to use * @param input the text to check for a secret code in - * @return true if a secret code was encountered + * @return true if a secret code was encountered and intent is sent out */ static boolean handleSecretCode(Context context, String input) { + // Must use system service on O+ to avoid using broadcasts, which are not allowed on O+. + if (BuildCompat.isAtLeastO()) { + return context.getSystemService(TelephonyManager.class).sendDialerCode(input); + } + + // System service call is not supported pre-O, so must use a broadcast for N-. // Secret codes are in the form *#*##*#* int len = input.length(); if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) { @@ -144,7 +157,6 @@ public class SpecialCharSequenceMgr { context.sendBroadcast(intent); return true; } - return false; } @@ -237,7 +249,7 @@ public class SpecialCharSequenceMgr { private static void handleAdnQuery(QueryHandler handler, SimContactQueryCookie cookie, Uri uri) { if (handler == null || cookie == null || uri == null) { - Log.w(TAG, "queryAdn parameters incorrect"); + LogUtil.w("SpecialCharSequenceMgr.handleAdnQuery", "queryAdn parameters incorrect"); return; } @@ -325,12 +337,14 @@ public class SpecialCharSequenceMgr { private static boolean handleRegulatoryInfoDisplay(Context context, String input) { if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) { - Log.d(TAG, "handleRegulatoryInfoDisplay() sending intent to settings app"); + LogUtil.i( + "SpecialCharSequenceMgr.handleRegulatoryInfoDisplay", "sending intent to settings app"); Intent showRegInfoIntent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO); try { context.startActivity(showRegInfoIntent); } catch (ActivityNotFoundException e) { - Log.e(TAG, "startActivity() failed: " + e); + LogUtil.e( + "SpecialCharSequenceMgr.handleRegulatoryInfoDisplay", "startActivity() failed: ", e); } return true; } diff --git a/java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java b/java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java deleted file mode 100644 index ab6ef7362..000000000 --- a/java/com/android/dialer/app/calllog/CallDetailHistoryAdapter.java +++ /dev/null @@ -1,214 +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.content.Context; -import android.icu.lang.UCharacter; -import android.icu.text.BreakIterator; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.provider.CallLog.Calls; -import android.text.format.DateUtils; -import android.text.format.Formatter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; -import com.android.dialer.app.PhoneCallDetails; -import com.android.dialer.app.R; -import com.android.dialer.util.CallUtil; -import com.android.dialer.util.DialerUtils; -import java.util.ArrayList; -import java.util.Locale; - -/** Adapter for a ListView containing history items from the details of a call. */ -public class CallDetailHistoryAdapter extends BaseAdapter { - - /** Each history item shows the detail of a call. */ - private static final int VIEW_TYPE_HISTORY_ITEM = 1; - - private final Context mContext; - private final LayoutInflater mLayoutInflater; - private final CallTypeHelper mCallTypeHelper; - private final PhoneCallDetails[] mPhoneCallDetails; - - /** List of items to be concatenated together for duration strings. */ - private ArrayList mDurationItems = new ArrayList<>(); - - public CallDetailHistoryAdapter( - Context context, - LayoutInflater layoutInflater, - CallTypeHelper callTypeHelper, - PhoneCallDetails[] phoneCallDetails) { - mContext = context; - mLayoutInflater = layoutInflater; - mCallTypeHelper = callTypeHelper; - mPhoneCallDetails = phoneCallDetails; - } - - @Override - public boolean isEnabled(int position) { - // None of history will be clickable. - return false; - } - - @Override - public int getCount() { - return mPhoneCallDetails.length; - } - - @Override - public Object getItem(int position) { - return mPhoneCallDetails[position]; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public int getItemViewType(int position) { - return VIEW_TYPE_HISTORY_ITEM; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // Make sure we have a valid convertView to start with - final View result = - convertView == null - ? mLayoutInflater.inflate(R.layout.call_detail_history_item, parent, false) - : convertView; - - PhoneCallDetails details = mPhoneCallDetails[position]; - CallTypeIconsView callTypeIconView = - (CallTypeIconsView) result.findViewById(R.id.call_type_icon); - TextView callTypeTextView = (TextView) result.findViewById(R.id.call_type_text); - TextView dateView = (TextView) result.findViewById(R.id.date); - TextView durationView = (TextView) result.findViewById(R.id.duration); - - int callType = details.callTypes[0]; - boolean isVideoCall = - (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO - && CallUtil.isVideoEnabled(mContext); - boolean isPulledCall = - (details.features & Calls.FEATURES_PULLED_EXTERNALLY) == Calls.FEATURES_PULLED_EXTERNALLY; - - callTypeIconView.clear(); - callTypeIconView.add(callType); - callTypeIconView.setShowVideo(isVideoCall); - callTypeTextView.setText(mCallTypeHelper.getCallTypeText(callType, isVideoCall, isPulledCall)); - // Set the date. - dateView.setText(formatDate(details.date)); - // Set the duration - if (Calls.VOICEMAIL_TYPE == callType || CallTypeHelper.isMissedCallType(callType)) { - durationView.setVisibility(View.GONE); - } else { - durationView.setVisibility(View.VISIBLE); - durationView.setText(formatDurationAndDataUsage(details.duration, details.dataUsage)); - } - - return result; - } - - /** - * Formats the provided date into a value suitable for display in the current locale. - * - *

For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 - * may 25,20:02". - * - *

For pre-N devices, the returned value may not start with a capital if the local convention - * is to not capitalize day names. On N+ devices, the returned value is always capitalized. - */ - private CharSequence formatDate(long callDateMillis) { - CharSequence dateValue = - DateUtils.formatDateRange( - mContext, - callDateMillis /* startDate */, - callDateMillis /* endDate */, - DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_WEEKDAY - | DateUtils.FORMAT_SHOW_YEAR); - - // We want the beginning of the date string to be capitalized, even if the word at the beginning - // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” - // (not capitalized). To handle this issue we apply title casing to the start of the sentence so - // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". - // - // The ICU library was not available in Android until N, so we can only do this in N+ devices. - // Pre-N devices will still see incorrect capitalization in some languages. - if (VERSION.SDK_INT < VERSION_CODES.N) { - return dateValue; - } - - // Using the ICU library is safer than just applying toUpperCase() on the first letter of the - // word because in some languages, there can be multiple starting characters which should be - // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be - // capitalized together. - - // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized like the - // month ("May") are not lower-cased as part of the conversion. - return UCharacter.toTitleCase( - Locale.getDefault(), - dateValue.toString(), - BreakIterator.getSentenceInstance(), - UCharacter.TITLECASE_NO_LOWERCASE); - } - - private CharSequence formatDuration(long elapsedSeconds) { - long minutes = 0; - long seconds = 0; - - if (elapsedSeconds >= 60) { - minutes = elapsedSeconds / 60; - elapsedSeconds -= minutes * 60; - seconds = elapsedSeconds; - return mContext.getString(R.string.callDetailsDurationFormat, minutes, seconds); - } else { - seconds = elapsedSeconds; - return mContext.getString(R.string.callDetailsShortDurationFormat, seconds); - } - } - - /** - * Formats a string containing the call duration and the data usage (if specified). - * - * @param elapsedSeconds Total elapsed seconds. - * @param dataUsage Data usage in bytes, or null if not specified. - * @return String containing call duration and data usage. - */ - private CharSequence formatDurationAndDataUsage(long elapsedSeconds, Long dataUsage) { - CharSequence duration = formatDuration(elapsedSeconds); - - if (dataUsage != null) { - mDurationItems.clear(); - mDurationItems.add(duration); - mDurationItems.add(Formatter.formatShortFileSize(mContext, dataUsage)); - - return DialerUtils.join(mDurationItems); - } else { - return duration; - } - } -} diff --git a/java/com/android/dialer/app/calllog/CallLogActivity.java b/java/com/android/dialer/app/calllog/CallLogActivity.java new file mode 100644 index 000000000..719ab4369 --- /dev/null +++ b/java/com/android/dialer/app/calllog/CallLogActivity.java @@ -0,0 +1,220 @@ +/* + * 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; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Intent; +import android.os.Bundle; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.support.v13.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +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.database.CallLogQueryHandler; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.ScreenEvent; +import com.android.dialer.util.TransactionSafeActivity; +import com.android.dialer.util.ViewUtil; + +/** Activity for viewing call history. */ +public class CallLogActivity extends TransactionSafeActivity + implements ViewPager.OnPageChangeListener { + + private static final int TAB_INDEX_ALL = 0; + private static final int TAB_INDEX_MISSED = 1; + private static final int TAB_INDEX_COUNT = 2; + private ViewPager mViewPager; + private ViewPagerTabs mViewPagerTabs; + private ViewPagerAdapter mViewPagerAdapter; + private CallLogFragment mAllCallsFragment; + private CallLogFragment mMissedCallsFragment; + private String[] mTabTitles; + private boolean mIsResumed; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.call_log_activity); + getWindow().setBackgroundDrawable(null); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowHomeEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setElevation(0); + + int startingTab = TAB_INDEX_ALL; + final Intent intent = getIntent(); + if (intent != null) { + final int callType = intent.getIntExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, -1); + if (callType == CallLog.Calls.MISSED_TYPE) { + startingTab = TAB_INDEX_MISSED; + } + } + + mTabTitles = new String[TAB_INDEX_COUNT]; + mTabTitles[0] = getString(R.string.call_log_all_title); + mTabTitles[1] = getString(R.string.call_log_missed_title); + + mViewPager = (ViewPager) findViewById(R.id.call_log_pager); + + mViewPagerAdapter = new ViewPagerAdapter(getFragmentManager()); + mViewPager.setAdapter(mViewPagerAdapter); + mViewPager.setOffscreenPageLimit(1); + mViewPager.setOnPageChangeListener(this); + + mViewPagerTabs = (ViewPagerTabs) findViewById(R.id.viewpager_header); + + mViewPagerTabs.setViewPager(mViewPager); + mViewPager.setCurrentItem(startingTab); + } + + @Override + protected void onResume() { + mIsResumed = true; + super.onResume(); + sendScreenViewForChildFragment(mViewPager.getCurrentItem()); + } + + @Override + protected void onPause() { + mIsResumed = false; + super.onPause(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.call_log_options, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all); + if (mAllCallsFragment != null && itemDeleteAll != null) { + // If onPrepareOptionsMenu is called before fragments are loaded, don't do anything. + final CallLogAdapter adapter = mAllCallsFragment.getAdapter(); + itemDeleteAll.setVisible(adapter != null && !adapter.isEmpty()); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (!isSafeToCommitTransactions()) { + return true; + } + + if (item.getItemId() == android.R.id.home) { + final Intent intent = new Intent(this, DialtactsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + } else if (item.getItemId() == R.id.delete_all) { + ClearCallLogDialog.show(getFragmentManager()); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mViewPagerTabs.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + + @Override + public void onPageSelected(int position) { + if (mIsResumed) { + sendScreenViewForChildFragment(position); + } + mViewPagerTabs.onPageSelected(position); + } + + @Override + public void onPageScrollStateChanged(int state) { + mViewPagerTabs.onPageScrollStateChanged(state); + } + + private void sendScreenViewForChildFragment(int position) { + Logger.get(this).logScreenView(ScreenEvent.Type.CALL_LOG_FILTER, this); + } + + private int getRtlPosition(int position) { + if (ViewUtil.isRtl()) { + return mViewPagerAdapter.getCount() - 1 - position; + } + return position; + } + + /** Adapter for the view pager. */ + public class ViewPagerAdapter extends FragmentPagerAdapter { + + public ViewPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public long getItemId(int position) { + return getRtlPosition(position); + } + + @Override + public Fragment getItem(int position) { + switch (getRtlPosition(position)) { + case TAB_INDEX_ALL: + return new CallLogFragment( + CallLogQueryHandler.CALL_TYPE_ALL, true /* isCallLogActivity */); + case TAB_INDEX_MISSED: + return new CallLogFragment(Calls.MISSED_TYPE, true /* isCallLogActivity */); + } + 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: + mAllCallsFragment = fragment; + break; + case TAB_INDEX_MISSED: + mMissedCallsFragment = fragment; + break; + } + return fragment; + } + + @Override + public CharSequence getPageTitle(int position) { + return mTabTitles[position]; + } + + @Override + public int getCount() { + return TAB_INDEX_COUNT; + } + } +} diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java index ea09a8c0a..fc5ffbb29 100644 --- a/java/com/android/dialer/app/calllog/CallLogAdapter.java +++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java @@ -47,7 +47,6 @@ 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.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogGroupBuilder.GroupCreator; import com.android.dialer.app.calllog.calllogcache.CallLogCache; @@ -55,13 +54,19 @@ import com.android.dialer.app.contactinfo.ContactInfoCache; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter.OnVoicemailDeletedListener; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; +import com.android.dialer.calldetails.nano.CallDetailsEntries; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calllogutils.PhoneAccountUtils; +import com.android.dialer.calllogutils.PhoneCallDetails; import com.android.dialer.common.Assert; import com.android.dialer.common.AsyncTaskExecutor; import com.android.dialer.common.AsyncTaskExecutors; import com.android.dialer.common.LogUtil; import com.android.dialer.enrichedcall.EnrichedCallCapabilities; +import com.android.dialer.enrichedcall.EnrichedCallComponent; import com.android.dialer.enrichedcall.EnrichedCallManager; import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener; +import com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult; import com.android.dialer.logging.Logger; import com.android.dialer.logging.nano.DialerImpression; import com.android.dialer.phonenumbercache.CallLogQuery; @@ -70,6 +75,7 @@ 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.List; import java.util.Map; import java.util.Set; @@ -96,7 +102,7 @@ public class CallLogAdapter extends GroupingListAdapter protected final CallLogCache mCallLogCache; private final CallFetcher mCallFetcher; - private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; + @NonNull private final FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; private final int mActivityType; /** Instance of helper class for managing views. */ @@ -182,8 +188,6 @@ public class CallLogAdapter extends GroupingListAdapter private boolean mIsSpamEnabled; - @NonNull private final EnrichedCallManager mEnrichedCallManager; - public CallLogAdapter( Activity activity, ViewGroup alertContainer, @@ -191,6 +195,7 @@ public class CallLogAdapter extends GroupingListAdapter CallLogCache callLogCache, ContactInfoCache contactInfoCache, VoicemailPlaybackPresenter voicemailPlaybackPresenter, + @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType) { super(); @@ -218,7 +223,7 @@ public class CallLogAdapter extends GroupingListAdapter mCallLogListItemHelper = new CallLogListItemHelper(phoneCallDetailsHelper, resources, mCallLogCache); mCallLogGroupBuilder = new CallLogGroupBuilder(this); - mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(mActivity); + mFilteredNumberAsyncQueryHandler = Assert.isNotNull(filteredNumberAsyncQueryHandler); mContactsPreferences = new ContactsPreferences(mActivity); @@ -232,7 +237,6 @@ public class CallLogAdapter extends GroupingListAdapter mCallLogAlertManager = new CallLogAlertManager(this, LayoutInflater.from(mActivity), alertContainer); - mEnrichedCallManager = EnrichedCallManager.Accessor.getInstance(activity.getApplication()); } private void expandViewHolderActions(CallLogListItemViewHolder viewHolder) { @@ -296,7 +300,7 @@ public class CallLogAdapter extends GroupingListAdapter } mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); mIsSpamEnabled = Spam.get(mActivity).isSpamEnabled(); - mEnrichedCallManager.registerCapabilitiesListener(this); + getEnrichedCallManager().registerCapabilitiesListener(this); notifyDataSetChanged(); } @@ -305,11 +309,11 @@ public class CallLogAdapter extends GroupingListAdapter for (Uri uri : mHiddenItemUris) { CallLogAsyncTaskUtil.deleteVoicemail(mActivity, uri, null); } - mEnrichedCallManager.unregisterCapabilitiesListener(this); + getEnrichedCallManager().unregisterCapabilitiesListener(this); } public void onStop() { - mEnrichedCallManager.clearCachedData(); + getEnrichedCallManager().clearCachedData(); } public CallLogAlertManager getAlertManager() { @@ -420,7 +424,9 @@ public class CallLogAdapter extends GroupingListAdapter } CallLogListItemViewHolder views = (CallLogListItemViewHolder) viewHolder; views.isLoaded = false; - PhoneCallDetails details = createPhoneCallDetails(c, getGroupSize(position), views); + int groupSize = getGroupSize(position); + CallDetailsEntries callDetailsEntries = createCallDetailsEntries(c, groupSize); + PhoneCallDetails details = createPhoneCallDetails(c, groupSize, views); if (mHiddenRowIds.contains(c.getLong(CallLogQuery.ID))) { views.callLogEntryView.setVisibility(View.GONE); views.dayGroupHeader.setVisibility(View.GONE); @@ -432,11 +438,14 @@ public class CallLogAdapter extends GroupingListAdapter if (mCurrentlyExpandedRowId == views.rowId) { views.inflateActionViewStub(); } - loadAndRender(views, views.rowId, details); + loadAndRender(views, views.rowId, details, callDetailsEntries); } private void loadAndRender( - final CallLogListItemViewHolder views, final long rowId, final PhoneCallDetails details) { + final CallLogListItemViewHolder views, + final long rowId, + final PhoneCallDetails details, + final CallDetailsEntries callDetailsEntries) { // Reset block and spam information since this view could be reused which may contain // outdated data. views.isSpam = false; @@ -464,12 +473,33 @@ public class CallLogAdapter extends GroupingListAdapter && Spam.get(mActivity) .checkSpamStatusSynchronous(views.number, views.countryIso); details.isSpam = views.isSpam; - if (isCancelled()) { - return false; + } + if (isCancelled()) { + return false; + } + setCallDetailsEntriesHistoryResults( + PhoneNumberUtils.formatNumberToE164(views.number, views.countryIso), + callDetailsEntries); + views.setDetailedPhoneDetails(callDetailsEntries); + return !isCancelled() && loadData(views, rowId, details); + } + + private void setCallDetailsEntriesHistoryResults( + @Nullable String number, CallDetailsEntries callDetailsEntries) { + if (number == null) { + return; + } + Map> mappedResults = + getEnrichedCallManager().getAllHistoricalData(number, callDetailsEntries); + for (CallDetailsEntry entry : callDetailsEntries.entries) { + List results = mappedResults.get(entry); + if (results != null) { + entry.historyResults = mappedResults.get(entry).toArray(new HistoryResult[0]); + LogUtil.v( + "CallLogAdapter.setCallDetailsEntriesHistoryResults", + "mapped %d results", + entry.historyResults.length); } - return loadData(views, rowId, details); - } else { - return loadData(views, rowId, details); } } @@ -499,9 +529,9 @@ public class CallLogAdapter extends GroupingListAdapter return false; } - EnrichedCallCapabilities capabilities = mEnrichedCallManager.getCapabilities(e164Number); + EnrichedCallCapabilities capabilities = getEnrichedCallManager().getCapabilities(e164Number); if (capabilities == null) { - mEnrichedCallManager.requestCapabilities(e164Number); + getEnrichedCallManager().requestCapabilities(e164Number); return false; } return capabilities.supportsCallComposer(); @@ -562,6 +592,27 @@ public class CallLogAdapter extends GroupingListAdapter return details; } + @MainThread + private static CallDetailsEntries createCallDetailsEntries(Cursor cursor, int count) { + Assert.isMainThread(); + int position = cursor.getPosition(); + CallDetailsEntries entries = new CallDetailsEntries(); + entries.entries = new CallDetailsEntry[count]; + for (int i = 0; i < count; i++) { + CallDetailsEntry entry = new CallDetailsEntry(); + entry.callId = cursor.getLong(CallLogQuery.ID); + entry.callType = cursor.getInt(CallLogQuery.CALL_TYPE); + entry.dataUsage = cursor.getLong(CallLogQuery.DATA_USAGE); + entry.date = cursor.getLong(CallLogQuery.DATE); + entry.duration = cursor.getLong(CallLogQuery.DURATION); + entry.features |= cursor.getInt(CallLogQuery.FEATURES); + entries.entries[i] = entry; + cursor.moveToNext(); + } + cursor.moveToPosition(position); + return entries; + } + /** * Load data for call log. Any expensive operation should be put here to avoid blocking main * thread. Do NOT put any cursor operation here since it's not thread safe. @@ -907,6 +958,11 @@ public class CallLogAdapter extends GroupingListAdapter notifyDataSetChanged(); } + @NonNull + private EnrichedCallManager getEnrichedCallManager() { + return EnrichedCallComponent.get(mActivity).getEnrichedCallManager(); + } + /** Interface used to initiate a refresh of the content. */ public interface CallFetcher { diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java index b4e6fc5ad..2198626d6 100644 --- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java +++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java @@ -16,37 +16,22 @@ package com.android.dialer.app.calllog; -import android.Manifest.permission; import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; -import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.CallLog; import android.provider.VoicemailContract.Voicemails; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.support.v4.content.ContextCompat; -import android.telecom.PhoneAccountHandle; import android.text.TextUtils; -import com.android.contacts.common.GeoUtil; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.common.AsyncTaskExecutor; import com.android.dialer.common.AsyncTaskExecutors; -import com.android.dialer.common.LogUtil; -import com.android.dialer.phonenumbercache.ContactInfo; -import com.android.dialer.phonenumbercache.ContactInfoHelper; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; -import java.util.ArrayList; -import java.util.Arrays; +import com.android.voicemail.VoicemailClient; @TargetApi(VERSION_CODES.M) public class CallLogAsyncTaskUtil { @@ -58,166 +43,6 @@ public class CallLogAsyncTaskUtil { sAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); } - public static void getCallDetails( - @NonNull final Context context, - @Nullable final CallLogAsyncTaskListener callLogAsyncTaskListener, - @NonNull final Uri... callUris) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit( - Tasks.GET_CALL_DETAILS, - new AsyncTask() { - @Override - public PhoneCallDetails[] doInBackground(Void... params) { - if (ContextCompat.checkSelfPermission(context, permission.READ_CALL_LOG) - != PackageManager.PERMISSION_GRANTED) { - LogUtil.w("CallLogAsyncTaskUtil.getCallDetails", "missing READ_CALL_LOG permission"); - return null; - } - // TODO: All calls correspond to the same person, so make a single lookup. - final int numCalls = callUris.length; - PhoneCallDetails[] details = new PhoneCallDetails[numCalls]; - try { - for (int index = 0; index < numCalls; ++index) { - details[index] = getPhoneCallDetailsForUri(context, callUris[index]); - } - return details; - } catch (IllegalArgumentException e) { - // Something went wrong reading in our primary data. - LogUtil.e( - "CallLogAsyncTaskUtil.getCallDetails", "invalid URI starting call details", e); - return null; - } - } - - @Override - public void onPostExecute(PhoneCallDetails[] phoneCallDetails) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onGetCallDetails(phoneCallDetails); - } - } - }); - } - - /** Return the phone call details for a given call log URI. */ - private static PhoneCallDetails getPhoneCallDetailsForUri( - @NonNull Context context, @NonNull Uri callUri) { - Cursor cursor = - context - .getContentResolver() - .query(callUri, CallDetailQuery.CALL_LOG_PROJECTION, null, null, null); - - try { - if (cursor == null || !cursor.moveToFirst()) { - throw new IllegalArgumentException("Cannot find content: " + callUri); - } - - // Read call log. - final String countryIso = cursor.getString(CallDetailQuery.COUNTRY_ISO_COLUMN_INDEX); - final String number = cursor.getString(CallDetailQuery.NUMBER_COLUMN_INDEX); - final String postDialDigits = - (VERSION.SDK_INT >= VERSION_CODES.N) - ? cursor.getString(CallDetailQuery.POST_DIAL_DIGITS) - : ""; - final String viaNumber = - (VERSION.SDK_INT >= VERSION_CODES.N) ? cursor.getString(CallDetailQuery.VIA_NUMBER) : ""; - final int numberPresentation = - cursor.getInt(CallDetailQuery.NUMBER_PRESENTATION_COLUMN_INDEX); - - final PhoneAccountHandle accountHandle = - PhoneAccountUtils.getAccount( - cursor.getString(CallDetailQuery.ACCOUNT_COMPONENT_NAME), - cursor.getString(CallDetailQuery.ACCOUNT_ID)); - - // If this is not a regular number, there is no point in looking it up in the contacts. - ContactInfoHelper contactInfoHelper = - new ContactInfoHelper(context, GeoUtil.getCurrentCountryIso(context)); - boolean isVoicemail = PhoneNumberHelper.isVoicemailNumber(context, accountHandle, number); - boolean shouldLookupNumber = - PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation) && !isVoicemail; - ContactInfo info = ContactInfo.EMPTY; - - if (shouldLookupNumber) { - ContactInfo lookupInfo = contactInfoHelper.lookupNumber(number, countryIso); - info = lookupInfo != null ? lookupInfo : ContactInfo.EMPTY; - } - - PhoneCallDetails details = new PhoneCallDetails(number, numberPresentation, postDialDigits); - details.updateDisplayNumber(context, info.formattedNumber, isVoicemail); - - details.viaNumber = viaNumber; - details.accountHandle = accountHandle; - details.contactUri = info.lookupUri; - details.namePrimary = info.name; - details.nameAlternative = info.nameAlternative; - details.numberType = info.type; - details.numberLabel = info.label; - details.photoUri = info.photoUri; - details.sourceType = info.sourceType; - details.objectId = info.objectId; - - details.callTypes = new int[] {cursor.getInt(CallDetailQuery.CALL_TYPE_COLUMN_INDEX)}; - details.date = cursor.getLong(CallDetailQuery.DATE_COLUMN_INDEX); - details.duration = cursor.getLong(CallDetailQuery.DURATION_COLUMN_INDEX); - details.features = cursor.getInt(CallDetailQuery.FEATURES); - details.geocode = cursor.getString(CallDetailQuery.GEOCODED_LOCATION_COLUMN_INDEX); - details.transcription = cursor.getString(CallDetailQuery.TRANSCRIPTION_COLUMN_INDEX); - - details.countryIso = - !TextUtils.isEmpty(countryIso) ? countryIso : GeoUtil.getCurrentCountryIso(context); - - if (!cursor.isNull(CallDetailQuery.DATA_USAGE)) { - details.dataUsage = cursor.getLong(CallDetailQuery.DATA_USAGE); - } - - return details; - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - /** - * Delete specified calls from the call log. - * - * @param context The context. - * @param callIds String of the callIds to delete from the call log, delimited by commas (","). - * @param callLogAsyncTaskListener The listener to invoke after the entries have been deleted. - */ - public static void deleteCalls( - @NonNull final Context context, - final String callIds, - @Nullable final CallLogAsyncTaskListener callLogAsyncTaskListener) { - if (sAsyncTaskExecutor == null) { - initTaskExecutor(); - } - - sAsyncTaskExecutor.submit( - Tasks.DELETE_CALL, - new AsyncTask() { - @Override - public Void doInBackground(Void... params) { - context - .getContentResolver() - .delete( - TelecomUtil.getCallLogUri(context), - CallLog.Calls._ID + " IN (" + callIds + ")", - null); - return null; - } - - @Override - public void onPostExecute(Void result) { - if (callLogAsyncTaskListener != null) { - callLogAsyncTaskListener.onDeleteCall(); - } - } - }); - } - public static void markVoicemailAsRead( @NonNull final Context context, @NonNull final Uri voicemailUri) { if (sAsyncTaskExecutor == null) { @@ -235,6 +60,8 @@ public class CallLogAsyncTaskUtil { .getContentResolver() .update(voicemailUri, values, Voicemails.IS_READ + " = 0", null); + uploadVoicemailLocalChangesToServer(context); + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); context.startService(intent); @@ -256,7 +83,12 @@ public class CallLogAsyncTaskUtil { new AsyncTask() { @Override public Void doInBackground(Void... params) { - context.getContentResolver().delete(voicemailUri, null, null); + ContentValues values = new ContentValues(); + values.put(Voicemails.DELETED, "1"); + context.getContentResolver().update(voicemailUri, values, null, null); + // TODO(b/35440541): check which source package is changed. Don't need + // to upload changes on foreign voicemails, they will get a PROVIDER_CHANGED + uploadVoicemailLocalChangesToServer(context); return null; } @@ -305,11 +137,6 @@ public class CallLogAsyncTaskUtil { }); } - @VisibleForTesting - public static void resetForTest() { - sAsyncTaskExecutor = null; - } - /** The enumeration of {@link AsyncTask} objects used in this class. */ public enum Tasks { DELETE_VOICEMAIL, @@ -321,56 +148,12 @@ public class CallLogAsyncTaskUtil { } public interface CallLogAsyncTaskListener { - - void onDeleteCall(); - void onDeleteVoicemail(); - - void onGetCallDetails(PhoneCallDetails[] details); } - private static final class CallDetailQuery { - - public static final String[] CALL_LOG_PROJECTION; - static final int DATE_COLUMN_INDEX = 0; - static final int DURATION_COLUMN_INDEX = 1; - static final int NUMBER_COLUMN_INDEX = 2; - static final int CALL_TYPE_COLUMN_INDEX = 3; - static final int COUNTRY_ISO_COLUMN_INDEX = 4; - static final int GEOCODED_LOCATION_COLUMN_INDEX = 5; - static final int NUMBER_PRESENTATION_COLUMN_INDEX = 6; - static final int ACCOUNT_COMPONENT_NAME = 7; - static final int ACCOUNT_ID = 8; - static final int FEATURES = 9; - static final int DATA_USAGE = 10; - static final int TRANSCRIPTION_COLUMN_INDEX = 11; - static final int POST_DIAL_DIGITS = 12; - static final int VIA_NUMBER = 13; - private static final String[] CALL_LOG_PROJECTION_INTERNAL = - new String[] { - CallLog.Calls.DATE, - CallLog.Calls.DURATION, - CallLog.Calls.NUMBER, - CallLog.Calls.TYPE, - CallLog.Calls.COUNTRY_ISO, - CallLog.Calls.GEOCODED_LOCATION, - CallLog.Calls.NUMBER_PRESENTATION, - CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME, - CallLog.Calls.PHONE_ACCOUNT_ID, - CallLog.Calls.FEATURES, - CallLog.Calls.DATA_USAGE, - CallLog.Calls.TRANSCRIPTION - }; - - static { - ArrayList projectionList = new ArrayList<>(); - projectionList.addAll(Arrays.asList(CALL_LOG_PROJECTION_INTERNAL)); - if (VERSION.SDK_INT >= VERSION_CODES.N) { - projectionList.add(CallLog.Calls.POST_DIAL_DIGITS); - projectionList.add(CallLog.Calls.VIA_NUMBER); - } - projectionList.trimToSize(); - CALL_LOG_PROJECTION = projectionList.toArray(new String[projectionList.size()]); - } + private static void uploadVoicemailLocalChangesToServer(Context context) { + Intent intent = new Intent(VoicemailClient.ACTION_UPLOAD); + intent.setPackage(context.getPackageName()); + context.sendBroadcast(intent); } } diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java index 1ae68cd65..4abef3430 100644 --- a/java/com/android/dialer/app/calllog/CallLogFragment.java +++ b/java/com/android/dialer/app/calllog/CallLogFragment.java @@ -53,6 +53,7 @@ import com.android.dialer.app.list.ListsFragment.ListsPage; 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.LogUtil; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.phonenumbercache.ContactInfoHelper; @@ -70,9 +71,17 @@ public class CallLogFragment extends Fragment FragmentCompat.OnRequestPermissionsResultCallback, CallLogModalAlertManager.Listener { 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"; + // 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 EVENT_UPDATE_DISPLAY = 1; @@ -104,8 +113,17 @@ public class CallLogFragment extends Fragment // Exactly same variable is in Fragment as a package private. private boolean mMenuVisible = true; // Default to all calls. - protected int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL; - + private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL; + // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler} + // will be used. + private int mLogLimit = NO_LOG_LIMIT; + // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after + // the date filter are included. If zero, no date-based filtering occurs. + private long mDateLimit = NO_DATE_LIMIT; + /* + * True if this instance of the CallLogFragment shown in the CallLogActivity. + */ + private boolean mIsCallLogActivity = false; private final Handler mDisplayUpdateHandler = new Handler() { @Override @@ -121,6 +139,48 @@ public class CallLogFragment extends Fragment protected CallLogModalAlertManager mModalAlertManager; private ViewGroup mModalAlertView; + public CallLogFragment() { + this(CallLogQueryHandler.CALL_TYPE_ALL, NO_LOG_LIMIT); + } + + public CallLogFragment(int filterType) { + this(filterType, NO_LOG_LIMIT); + } + + public CallLogFragment(int filterType, boolean isCallLogActivity) { + this(filterType, NO_LOG_LIMIT); + mIsCallLogActivity = isCallLogActivity; + } + + public CallLogFragment(int filterType, int logLimit) { + this(filterType, logLimit, NO_DATE_LIMIT); + } + + /** + * Creates a call log fragment, filtering to include only calls of the desired type, occurring + * after the specified date. + * + * @param filterType type of calls to include. + * @param dateLimit limits results to calls occurring on or after the specified date. + */ + public CallLogFragment(int filterType, long dateLimit) { + this(filterType, NO_LOG_LIMIT, dateLimit); + } + + /** + * Creates a call log fragment, filtering to include only calls of the desired type, occurring + * after the specified date. Also provides a means to limit the number of results returned. + * + * @param filterType type of calls to include. + * @param logLimit limits the number of results to return. + * @param dateLimit limits results to calls occurring on or after the specified date. + */ + public CallLogFragment(int filterType, int logLimit, long dateLimit) { + mCallTypeFilter = filterType; + mLogLimit = logLimit; + mDateLimit = dateLimit; + } + @Override public void onCreate(Bundle state) { LogUtil.d("CallLogFragment.onCreate", toString()); @@ -128,13 +188,16 @@ public class CallLogFragment extends Fragment mRefreshDataRequired = true; if (state != null) { mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter); + mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit); + mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit); + 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); } final Activity activity = getActivity(); final ContentResolver resolver = activity.getContentResolver(); - mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this); + mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit); mKeyguardManager = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver); resolver.registerContentObserver( @@ -226,7 +289,10 @@ public class CallLogFragment extends Fragment } protected void setupData() { - int activityType = CallLogAdapter.ACTIVITY_TYPE_DIALTACTS; + int activityType = + mIsCallLogActivity + ? CallLogAdapter.ACTIVITY_TYPE_CALL_LOG + : CallLogAdapter.ACTIVITY_TYPE_DIALTACTS; String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); mContactInfoCache = @@ -244,6 +310,7 @@ public class CallLogFragment extends Fragment CallLogCache.getCallLogCache(getActivity()), mContactInfoCache, getVoicemailPlaybackPresenter(), + new FilteredNumberAsyncQueryHandler(getActivity()), activityType); mRecyclerView.setAdapter(mAdapter); fetchCalls(); @@ -324,6 +391,9 @@ public class CallLogFragment extends Fragment public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter); + outState.putInt(KEY_LOG_LIMIT, mLogLimit); + outState.putLong(KEY_DATE_LIMIT, mDateLimit); + outState.putBoolean(KEY_IS_CALL_LOG_ACTIVITY, mIsCallLogActivity); outState.putBoolean(KEY_HAS_READ_CALL_LOG_PERMISSION, mHasReadCallLogPermission); outState.putBoolean(KEY_REFRESH_DATA_REQUIRED, mRefreshDataRequired); @@ -334,8 +404,10 @@ public class CallLogFragment extends Fragment @Override public void fetchCalls() { - mCallLogQueryHandler.fetchCalls(mCallTypeFilter); - ((ListsFragment) getParentFragment()).updateTabUnreadCounts(); + mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit); + if (!mIsCallLogActivity) { + ((ListsFragment) getParentFragment()).updateTabUnreadCounts(); + } } private void updateEmptyMessage(int filterType) { @@ -366,7 +438,9 @@ public class CallLogFragment extends Fragment "Unexpected filter type in CallLogFragment: " + filterType); } mEmptyListView.setDescription(messageId); - if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) { + if (mIsCallLogActivity) { + mEmptyListView.setActionLabel(EmptyContentView.NO_LABEL); + } else if (filterType == CallLogQueryHandler.CALL_TYPE_ALL) { mEmptyListView.setActionLabel(R.string.call_log_all_empty_action); } } @@ -420,7 +494,7 @@ public class CallLogFragment extends Fragment if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode() && mCallTypeFilter == Calls.VOICEMAIL_TYPE) { - CallLogNotificationsHelper.updateVoicemailNotifications(getActivity()); + CallLogNotificationsQueryHelper.updateVoicemailNotifications(getActivity()); } } @@ -434,7 +508,8 @@ public class CallLogFragment extends Fragment if (!PermissionsUtil.hasPermission(activity, READ_CALL_LOG)) { FragmentCompat.requestPermissions( this, new String[] {READ_CALL_LOG}, READ_CALL_LOG_PERMISSION_REQUEST_CODE); - } else { + } else if (!mIsCallLogActivity) { + // Show dialpad if we are not in the call log activity. ((HostInterface) activity).showDialpad(); } } diff --git a/java/com/android/dialer/app/calllog/CallLogListItemHelper.java b/java/com/android/dialer/app/calllog/CallLogListItemHelper.java index ea2119c83..a5df8cca1 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemHelper.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemHelper.java @@ -21,18 +21,16 @@ import android.provider.CallLog.Calls; import android.support.annotation.WorkerThread; import android.text.SpannableStringBuilder; import android.text.TextUtils; -import android.util.Log; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.calllogcache.CallLogCache; +import com.android.dialer.calllogutils.PhoneCallDetails; import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; import com.android.dialer.compat.AppCompatConstants; /** Helper class to fill in the views of a call log entry. */ /* package */ class CallLogListItemHelper { - private static final String TAG = "CallLogListItemHelper"; - /** Helper for populating the details of a phone call. */ private final PhoneCallDetailsHelper mPhoneCallDetailsHelper; /** Resources to look up strings. */ @@ -105,7 +103,9 @@ import com.android.dialer.compat.AppCompatConstants; */ public void setActionContentDescriptions(CallLogListItemViewHolder views) { if (views.nameOrNumber == null) { - Log.e(TAG, "setActionContentDescriptions; name or number is null."); + LogUtil.e( + "CallLogListItemHelper.setActionContentDescriptions", + "setActionContentDescriptions; name or number is null."); } // Calling expandTemplate with a null parameter will cause a NullPointerException. @@ -170,7 +170,6 @@ import com.android.dialer.compat.AppCompatConstants; * *

2 calls. Answered call from John Doe mobile 1 hour ago. * - * @param context The application context. * @param details Details of call. * @return Return call action description. */ diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java index 6abd36078..8a2d94499 100644 --- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java +++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java @@ -25,9 +25,11 @@ import android.os.AsyncTask; import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.annotation.VisibleForTesting; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; @@ -56,7 +58,7 @@ import com.android.dialer.blocking.FilteredNumberCompat; import com.android.dialer.blocking.FilteredNumbersUtil; import com.android.dialer.callcomposer.CallComposerActivity; import com.android.dialer.callcomposer.nano.CallComposerContact; -import com.android.dialer.common.ConfigProviderBindings; +import com.android.dialer.calldetails.nano.CallDetailsEntries; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.CompatUtils; import com.android.dialer.logging.Logger; @@ -78,8 +80,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, MenuItem.OnMenuItemClickListener, View.OnCreateContextMenuListener { - private static final String CONFIG_SHARE_VOICEMAIL_ALLOWED = "share_voicemail_allowed"; - /** The root view of the call log list item */ public final View rootView; /** The quick contact badge for the contact. */ @@ -201,6 +201,7 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder public boolean isAttachedToWindow; public AsyncTask asyncTask; + private CallDetailsEntries callDetailsEntries; private CallLogListItemViewHolder( Context context, @@ -549,10 +550,6 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } - private static boolean isShareVoicemailAllowed(Context context) { - return ConfigProviderBindings.get(context).getBoolean(CONFIG_SHARE_VOICEMAIL_ALLOWED, true); - } - /** * Binds text titles, click handlers and intents to the voicemail, details and callback action * buttons. @@ -577,13 +574,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder unblockView.setVisibility(View.GONE); reportNotSpamView.setVisibility(View.GONE); - if (isShareVoicemailAllowed(mContext)) { - sendVoicemailButtonView.setVisibility(View.VISIBLE); - } voicemailPlaybackView.setVisibility(View.VISIBLE); Uri uri = Uri.parse(voicemailUri); mVoicemailPlaybackPresenter.setPlaybackView( - voicemailPlaybackView, rowId, uri, mVoicemailPrimaryActionButtonClicked); + voicemailPlaybackView, + rowId, + uri, + mVoicemailPrimaryActionButtonClicked, + sendVoicemailButtonView); mVoicemailPrimaryActionButtonClicked = false; CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); return; @@ -621,14 +619,14 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder && mVoicemailPlaybackPresenter != null && !TextUtils.isEmpty(voicemailUri)) { voicemailPlaybackView.setVisibility(View.VISIBLE); - if (isShareVoicemailAllowed(mContext)) { - Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_VISIBLE); - sendVoicemailButtonView.setVisibility(View.VISIBLE); - } Uri uri = Uri.parse(voicemailUri); mVoicemailPlaybackPresenter.setPlaybackView( - voicemailPlaybackView, rowId, uri, mVoicemailPrimaryActionButtonClicked); + voicemailPlaybackView, + rowId, + uri, + mVoicemailPrimaryActionButtonClicked, + sendVoicemailButtonView); mVoicemailPrimaryActionButtonClicked = false; CallLogAsyncTaskUtil.markVoicemailAsRead(mContext, uri); } else { @@ -640,7 +638,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder detailsButtonView.setVisibility(View.GONE); } else { detailsButtonView.setVisibility(View.VISIBLE); - detailsButtonView.setTag(IntentProvider.getCallDetailIntentProvider(rowId, callIds, null)); + detailsButtonView.setTag( + IntentProvider.getCallDetailIntentProvider(callDetailsEntries, buildContact())); } boolean isBlockedOrSpam = blockId != null || (isSpamFeatureEnabled && isSpam); @@ -776,6 +775,8 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder contactType = ContactPhotoManager.TYPE_VOICEMAIL; } else if (isBusiness) { contactType = ContactPhotoManager.TYPE_BUSINESS; + } else if (numberPresentation == TelecomManager.PRESENTATION_RESTRICTED) { + contactType = ContactPhotoManager.TYPE_GENERIC_AVATAR; } final String lookupKey = @@ -854,20 +855,9 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } else if (view.getId() == R.id.call_compose_action) { LogUtil.i("CallLogListItemViewHolder.onClick", "share and call pressed"); Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SHARE_AND_CALL); - CallComposerContact contact = new CallComposerContact(); - contact.photoId = info.photoId; - contact.photoUri = info.photoUri == null ? null : info.photoUri.toString(); - contact.contactUri = info.lookupUri == null ? null : info.lookupUri.toString(); - contact.nameOrNumber = (String) nameOrNumber; - contact.isBusiness = isBusiness; - contact.number = number; - /* second line of contact view. */ - contact.displayNumber = TextUtils.isEmpty(info.name) ? null : displayNumber; - /* phone number type (e.g. mobile) in second line of contact view */ - contact.numberLabel = numberType; Activity activity = (Activity) mContext; activity.startActivityForResult( - CallComposerActivity.newIntent(activity, contact), + CallComposerActivity.newIntent(activity, buildContact()), DialtactsActivity.ACTIVITY_REQUEST_CODE_CALL_COMPOSE); } else if (view.getId() == R.id.share_voicemail) { Logger.get(mContext).logImpression(DialerImpression.Type.VVM_SHARE_PRESSED); @@ -885,6 +875,21 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } + private CallComposerContact buildContact() { + CallComposerContact contact = new CallComposerContact(); + contact.photoId = info.photoId; + contact.photoUri = info.photoUri == null ? null : info.photoUri.toString(); + contact.contactUri = info.lookupUri == null ? null : info.lookupUri.toString(); + contact.nameOrNumber = (String) nameOrNumber; + contact.isBusiness = isBusiness; + contact.number = number; + /* second line of contact view. */ + contact.displayNumber = TextUtils.isEmpty(info.name) ? null : displayNumber; + /* phone number type (e.g. mobile) in second line of contact view */ + contact.numberLabel = numberType; + return contact; + } + private void logCallLogAction(int id) { if (id == R.id.send_message_action) { Logger.get(mContext).logImpression(DialerImpression.Type.CALL_LOG_SEND_MESSAGE); @@ -931,6 +936,15 @@ public final class CallLogListItemViewHolder extends RecyclerView.ViewHolder } } + public void setDetailedPhoneDetails(CallDetailsEntries callDetailsEntries) { + this.callDetailsEntries = callDetailsEntries; + } + + @VisibleForTesting + public CallDetailsEntries getDetailedPhoneDetails() { + return callDetailsEntries; + } + public interface OnClickListener { void onBlockReportSpam( diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsHelper.java deleted file mode 100644 index 8f664d1a4..000000000 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsHelper.java +++ /dev/null @@ -1,299 +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; - -import android.Manifest; -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build.VERSION_CODES; -import android.provider.CallLog.Calls; -import android.support.annotation.Nullable; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.util.Log; -import com.android.contacts.common.GeoUtil; -import com.android.dialer.app.R; -import com.android.dialer.phonenumbercache.ContactInfo; -import com.android.dialer.phonenumbercache.ContactInfoHelper; -import com.android.dialer.telecom.TelecomUtil; -import com.android.dialer.util.PermissionsUtil; -import java.util.ArrayList; -import java.util.List; - -/** Helper class operating on call log notifications. */ -public class CallLogNotificationsHelper { - - private static final String TAG = "CallLogNotifHelper"; - private static CallLogNotificationsHelper sInstance; - private final Context mContext; - private final NewCallsQuery mNewCallsQuery; - private final ContactInfoHelper mContactInfoHelper; - private final String mCurrentCountryIso; - - CallLogNotificationsHelper( - Context context, - NewCallsQuery newCallsQuery, - ContactInfoHelper contactInfoHelper, - String countryIso) { - mContext = context; - mNewCallsQuery = newCallsQuery; - mContactInfoHelper = contactInfoHelper; - mCurrentCountryIso = countryIso; - } - - /** Returns the singleton instance of the {@link CallLogNotificationsHelper}. */ - public static CallLogNotificationsHelper getInstance(Context context) { - if (sInstance == null) { - ContentResolver contentResolver = context.getContentResolver(); - String countryIso = GeoUtil.getCurrentCountryIso(context); - sInstance = - new CallLogNotificationsHelper( - context, - createNewCallsQuery(context, contentResolver), - new ContactInfoHelper(context, countryIso), - countryIso); - } - return sInstance; - } - - /** Removes the missed call notifications. */ - public static void removeMissedCallNotifications(Context context) { - TelecomUtil.cancelMissedCallsNotification(context); - } - - /** Update the voice mail notifications. */ - public static void updateVoicemailNotifications(Context context) { - CallLogNotificationsService.updateVoicemailNotifications(context, null); - } - - /** Create a new instance of {@link NewCallsQuery}. */ - public static NewCallsQuery createNewCallsQuery( - Context context, ContentResolver contentResolver) { - - return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver); - } - - /** - * Get all voicemails with the "new" flag set to 1. - * - * @return A list of NewCall objects where each object represents a new voicemail. - */ - @Nullable - public List getNewVoicemails() { - return mNewCallsQuery.query(Calls.VOICEMAIL_TYPE); - } - - /** - * Get all missed calls with the "new" flag set to 1. - * - * @return A list of NewCall objects where each object represents a new missed call. - */ - @Nullable - public List getNewMissedCalls() { - return mNewCallsQuery.query(Calls.MISSED_TYPE); - } - - /** - * Given a number and number information (presentation and country ISO), get the best name for - * display. If the name is empty but we have a special presentation, display that. Otherwise - * attempt to look it up in the database or the cache. If that fails, fall back to displaying the - * number. - */ - public String getName( - @Nullable String number, int numberPresentation, @Nullable String countryIso) { - return getContactInfo(number, numberPresentation, countryIso).name; - } - - /** - * Given a number and number information (presentation and country ISO), get {@link ContactInfo}. - * If the name is empty but we have a special presentation, display that. Otherwise attempt to - * look it up in the cache. If that fails, fall back to displaying the number. - */ - public ContactInfo getContactInfo( - @Nullable String number, int numberPresentation, @Nullable String countryIso) { - if (countryIso == null) { - countryIso = mCurrentCountryIso; - } - - number = (number == null) ? "" : number; - ContactInfo contactInfo = new ContactInfo(); - contactInfo.number = number; - contactInfo.formattedNumber = PhoneNumberUtils.formatNumber(number, countryIso); - // contactInfo.normalizedNumber is not PhoneNumberUtils.normalizeNumber. Read ContactInfo. - contactInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); - - // 1. Special number representation. - contactInfo.name = - PhoneNumberDisplayUtil.getDisplayName(mContext, number, numberPresentation, false) - .toString(); - if (!TextUtils.isEmpty(contactInfo.name)) { - return contactInfo; - } - - // 2. Look it up in the cache. - ContactInfo cachedContactInfo = mContactInfoHelper.lookupNumber(number, countryIso); - - if (cachedContactInfo != null && !TextUtils.isEmpty(cachedContactInfo.name)) { - return cachedContactInfo; - } - - if (!TextUtils.isEmpty(contactInfo.formattedNumber)) { - // 3. If we cannot lookup the contact, use the formatted number instead. - contactInfo.name = contactInfo.formattedNumber; - } else if (!TextUtils.isEmpty(number)) { - // 4. If number can't be formatted, use number. - contactInfo.name = number; - } else { - // 5. Otherwise, it's unknown number. - contactInfo.name = mContext.getResources().getString(R.string.unknown); - } - return contactInfo; - } - - /** Allows determining the new calls for which a notification should be generated. */ - public interface NewCallsQuery { - - /** Returns the new calls of a certain type for which a notification should be generated. */ - @Nullable - List query(int type); - } - - /** Information about a new voicemail. */ - public static final class NewCall { - - public final Uri callsUri; - public final Uri voicemailUri; - public final String number; - public final int numberPresentation; - public final String accountComponentName; - public final String accountId; - public final String transcription; - public final String countryIso; - public final long dateMs; - - public NewCall( - Uri callsUri, - Uri voicemailUri, - String number, - int numberPresentation, - String accountComponentName, - String accountId, - String transcription, - String countryIso, - long dateMs) { - this.callsUri = callsUri; - this.voicemailUri = voicemailUri; - this.number = number; - this.numberPresentation = numberPresentation; - this.accountComponentName = accountComponentName; - this.accountId = accountId; - this.transcription = transcription; - this.countryIso = countryIso; - this.dateMs = dateMs; - } - } - - /** - * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to notify - * about in the call log. - */ - private static final class DefaultNewCallsQuery implements NewCallsQuery { - - private static final String[] PROJECTION = { - Calls._ID, - Calls.NUMBER, - Calls.VOICEMAIL_URI, - Calls.NUMBER_PRESENTATION, - Calls.PHONE_ACCOUNT_COMPONENT_NAME, - Calls.PHONE_ACCOUNT_ID, - Calls.TRANSCRIPTION, - Calls.COUNTRY_ISO, - Calls.DATE - }; - 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; - private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 3; - private static final int PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX = 4; - private static final int PHONE_ACCOUNT_ID_COLUMN_INDEX = 5; - 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 final ContentResolver mContentResolver; - private final Context mContext; - - private DefaultNewCallsQuery(Context context, ContentResolver contentResolver) { - mContext = context; - mContentResolver = contentResolver; - } - - @Override - @Nullable - @TargetApi(VERSION_CODES.M) - public List query(int type) { - if (!PermissionsUtil.hasPermission(mContext, Manifest.permission.READ_CALL_LOG)) { - Log.w(TAG, "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); - final String[] selectionArgs = new String[] {Integer.toString(type)}; - try (Cursor cursor = - mContentResolver.query( - Calls.CONTENT_URI_WITH_VOICEMAIL, - PROJECTION, - selection, - selectionArgs, - Calls.DEFAULT_SORT_ORDER)) { - if (cursor == null) { - return null; - } - List newCalls = new ArrayList<>(); - while (cursor.moveToNext()) { - newCalls.add(createNewCallsFromCursor(cursor)); - } - return newCalls; - } catch (RuntimeException e) { - Log.w(TAG, "Exception when querying Contacts Provider for calls lookup"); - return null; - } - } - - /** 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); - Uri callsUri = - ContentUris.withAppendedId( - Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX)); - Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString); - return new NewCall( - callsUri, - voicemailUri, - cursor.getString(NUMBER_COLUMN_INDEX), - cursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX), - cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX), - cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX), - cursor.getString(TRANSCRIPTION_COLUMN_INDEX), - cursor.getString(COUNTRY_ISO_COLUMN_INDEX), - cursor.getLong(DATE_COLUMN_INDEX)); - } - } -} diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java new file mode 100644 index 000000000..f12837e6f --- /dev/null +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java @@ -0,0 +1,334 @@ +/* + * 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; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build.VERSION_CODES; +import android.provider.CallLog.Calls; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.support.v4.os.UserManagerCompat; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import com.android.contacts.common.GeoUtil; +import com.android.dialer.app.R; +import com.android.dialer.calllogutils.PhoneNumberDisplayUtil; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.GroupedNotificationUtil; +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.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; + private final String mCurrentCountryIso; + + CallLogNotificationsQueryHelper( + Context context, + NewCallsQuery newCallsQuery, + ContactInfoHelper contactInfoHelper, + String countryIso) { + mContext = context; + mNewCallsQuery = newCallsQuery; + mContactInfoHelper = contactInfoHelper; + mCurrentCountryIso = countryIso; + } + + /** Returns an instance of {@link CallLogNotificationsQueryHelper}. */ + public static CallLogNotificationsQueryHelper getInstance(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + String countryIso = GeoUtil.getCurrentCountryIso(context); + return new CallLogNotificationsQueryHelper( + context, + createNewCallsQuery(context, contentResolver), + new ContactInfoHelper(context, countryIso), + countryIso); + } + + /** + * Removes the missed call notifications and marks calls as read. If a callUri is provided, only + * that call is 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); + } + } + + GroupedNotificationUtil.removeNotification( + context.getSystemService(NotificationManager.class), + callUri != null ? callUri.toString() : null, + R.id.notification_missed_call, + MissedCallNotifier.NOTIFICATION_TAG); + } + + /** Update the voice mail notifications. */ + public static void updateVoicemailNotifications(Context context) { + CallLogNotificationsService.updateVoicemailNotifications(context); + } + + /** Create a new instance of {@link NewCallsQuery}. */ + public static NewCallsQuery createNewCallsQuery( + Context context, ContentResolver contentResolver) { + + return new DefaultNewCallsQuery(context.getApplicationContext(), contentResolver); + } + + /** + * Get all voicemails with the "new" flag set to 1. + * + * @return A list of NewCall objects where each object represents a new voicemail. + */ + @Nullable + public List getNewVoicemails() { + return mNewCallsQuery.query(Calls.VOICEMAIL_TYPE); + } + + /** + * Get all missed calls with the "new" flag set to 1. + * + * @return A list of NewCall objects where each object represents a new missed call. + */ + @Nullable + public List getNewMissedCalls() { + return mNewCallsQuery.query(Calls.MISSED_TYPE); + } + + /** + * Given a number and number information (presentation and country ISO), get the best name for + * display. If the name is empty but we have a special presentation, display that. Otherwise + * attempt to look it up in the database or the cache. If that fails, fall back to displaying the + * number. + */ + public String getName( + @Nullable String number, int numberPresentation, @Nullable String countryIso) { + return getContactInfo(number, numberPresentation, countryIso).name; + } + + /** + * Given a number and number information (presentation and country ISO), get {@link ContactInfo}. + * If the name is empty but we have a special presentation, display that. Otherwise attempt to + * look it up in the cache. If that fails, fall back to displaying the number. + */ + public ContactInfo getContactInfo( + @Nullable String number, int numberPresentation, @Nullable String countryIso) { + if (countryIso == null) { + countryIso = mCurrentCountryIso; + } + + number = (number == null) ? "" : number; + ContactInfo contactInfo = new ContactInfo(); + contactInfo.number = number; + contactInfo.formattedNumber = PhoneNumberUtils.formatNumber(number, countryIso); + // contactInfo.normalizedNumber is not PhoneNumberUtils.normalizeNumber. Read ContactInfo. + contactInfo.normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso); + + // 1. Special number representation. + contactInfo.name = + PhoneNumberDisplayUtil.getDisplayName(mContext, number, numberPresentation, false) + .toString(); + if (!TextUtils.isEmpty(contactInfo.name)) { + return contactInfo; + } + + // 2. Look it up in the cache. + ContactInfo cachedContactInfo = mContactInfoHelper.lookupNumber(number, countryIso); + + if (cachedContactInfo != null && !TextUtils.isEmpty(cachedContactInfo.name)) { + return cachedContactInfo; + } + + if (!TextUtils.isEmpty(contactInfo.formattedNumber)) { + // 3. If we cannot lookup the contact, use the formatted number instead. + contactInfo.name = contactInfo.formattedNumber; + } else if (!TextUtils.isEmpty(number)) { + // 4. If number can't be formatted, use number. + contactInfo.name = number; + } else { + // 5. Otherwise, it's unknown number. + contactInfo.name = mContext.getResources().getString(R.string.unknown); + } + return contactInfo; + } + + /** Allows determining the new calls for which a notification should be generated. */ + public interface NewCallsQuery { + + /** Returns the new calls of a certain type for which a notification should be generated. */ + @Nullable + List query(int type); + } + + /** Information about a new voicemail. */ + public static final class NewCall { + + public final Uri callsUri; + public final Uri voicemailUri; + public final String number; + public final int numberPresentation; + public final String accountComponentName; + public final String accountId; + public final String transcription; + public final String countryIso; + public final long dateMs; + + public NewCall( + Uri callsUri, + Uri voicemailUri, + String number, + int numberPresentation, + String accountComponentName, + String accountId, + String transcription, + String countryIso, + long dateMs) { + this.callsUri = callsUri; + this.voicemailUri = voicemailUri; + this.number = number; + this.numberPresentation = numberPresentation; + this.accountComponentName = accountComponentName; + this.accountId = accountId; + this.transcription = transcription; + this.countryIso = countryIso; + this.dateMs = dateMs; + } + } + + /** + * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to notify + * about in the call log. + */ + private static final class DefaultNewCallsQuery implements NewCallsQuery { + + private static final String[] PROJECTION = { + Calls._ID, + Calls.NUMBER, + Calls.VOICEMAIL_URI, + Calls.NUMBER_PRESENTATION, + Calls.PHONE_ACCOUNT_COMPONENT_NAME, + Calls.PHONE_ACCOUNT_ID, + Calls.TRANSCRIPTION, + Calls.COUNTRY_ISO, + Calls.DATE + }; + 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; + private static final int NUMBER_PRESENTATION_COLUMN_INDEX = 3; + private static final int PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX = 4; + private static final int PHONE_ACCOUNT_ID_COLUMN_INDEX = 5; + 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 final ContentResolver mContentResolver; + private final Context mContext; + + private DefaultNewCallsQuery(Context context, ContentResolver contentResolver) { + mContext = context; + mContentResolver = contentResolver; + } + + @Override + @Nullable + @TargetApi(VERSION_CODES.M) + public List 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."); + return null; + } + final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE); + final String[] selectionArgs = new String[] {Integer.toString(type)}; + try (Cursor cursor = + mContentResolver.query( + Calls.CONTENT_URI_WITH_VOICEMAIL, + PROJECTION, + selection, + selectionArgs, + Calls.DEFAULT_SORT_ORDER)) { + if (cursor == null) { + return null; + } + List newCalls = new ArrayList<>(); + while (cursor.moveToNext()) { + newCalls.add(createNewCallsFromCursor(cursor)); + } + return newCalls; + } catch (RuntimeException e) { + LogUtil.w(TAG, "Exception when querying Contacts Provider for calls lookup"); + return null; + } + } + + /** 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); + Uri callsUri = + ContentUris.withAppendedId( + Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX)); + Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString); + return new NewCall( + callsUri, + voicemailUri, + cursor.getString(NUMBER_COLUMN_INDEX), + cursor.getInt(NUMBER_PRESENTATION_COLUMN_INDEX), + cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME_COLUMN_INDEX), + cursor.getString(PHONE_ACCOUNT_ID_COLUMN_INDEX), + cursor.getString(TRANSCRIPTION_COLUMN_INDEX), + cursor.getString(COUNTRY_ISO_COLUMN_INDEX), + cursor.getLong(DATE_COLUMN_INDEX)); + } + } +} diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java index 820528126..b0d48eee5 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java @@ -20,6 +20,7 @@ import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.support.annotation.Nullable; import com.android.dialer.common.LogUtil; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; @@ -44,20 +45,9 @@ 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"; - /** - * Action to update voicemail notifications. - * - *

May include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}. - */ + /** Action to update voicemail notifications. */ public static final String ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS = "com.android.dialer.calllog.UPDATE_VOICEMAIL_NOTIFICATIONS"; - /** - * Extra to included with {@link #ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS} to identify the new - * voicemail that triggered an update. - * - *

It must be a {@link Uri}. - */ - public static final String EXTRA_NEW_VOICEMAIL_URI = "NEW_VOICEMAIL_URI"; /** * Action to update the missed call notifications. * @@ -66,9 +56,15 @@ public class CallLogNotificationsService extends IntentService { */ public static final String ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS = "com.android.dialer.calllog.UPDATE_MISSED_CALL_NOTIFICATIONS"; + /** 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"; + + /** Action to update missed call notifications with a post call note. */ + public 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"; @@ -92,6 +88,21 @@ public class CallLogNotificationsService extends IntentService { */ public static final String EXTRA_MISSED_CALL_COUNT = "MISSED_CALL_COUNT"; + /** + * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent a post call note. + * + *

It must be a {@link String} + */ + public 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 + * post call note came from. + * + *

It must be a {@link String} + */ + public static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER"; + public static final int UNKNOWN_MISSED_CALL_COUNT = -1; private VoicemailQueryHandler mVoicemailQueryHandler; @@ -103,10 +114,8 @@ public class CallLogNotificationsService extends IntentService { * Updates notifications for any new voicemails. * * @param context a valid context. - * @param voicemailUri The uri pointing to the voicemail to update the notification for. If {@code - * null}, then notifications for all new voicemails will be updated. */ - public static void updateVoicemailNotifications(Context context, Uri voicemailUri) { + public static void updateVoicemailNotifications(Context context) { if (!TelecomUtil.isDefaultDialer(context)) { LogUtil.i( "CallLogNotificationsService.updateVoicemailNotifications", @@ -116,10 +125,6 @@ public class CallLogNotificationsService extends IntentService { if (TelecomUtil.hasReadWriteVoicemailPermissions(context)) { Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS); - // If voicemailUri is null, then notifications for all voicemails will be updated. - if (voicemailUri != null) { - serviceIntent.putExtra(CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, voicemailUri); - } context.startService(serviceIntent); } } @@ -139,9 +144,25 @@ public class CallLogNotificationsService extends IntentService { context.startService(serviceIntent); } - public static void markNewVoicemailsAsOld(Context context) { + public static void insertPostCallNote(Context context, String number, String postCallNote) { + Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); + serviceIntent.setAction(ACTION_INCOMING_POST_CALL); + serviceIntent.putExtra(EXTRA_POST_CALL_NUMBER, number); + serviceIntent.putExtra(EXTRA_POST_CALL_NOTE, postCallNote); + context.startService(serviceIntent); + } + + public static void markNewVoicemailsAsOld(Context context, @Nullable Uri voicemailUri) { Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); serviceIntent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); + serviceIntent.setData(voicemailUri); + context.startService(serviceIntent); + } + + public static void markNewMissedCallsAsOld(Context context, @Nullable Uri callUri) { + Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); + serviceIntent.setAction(ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); + serviceIntent.setData(callUri); context.startService(serviceIntent); } @@ -172,11 +193,10 @@ public class CallLogNotificationsService extends IntentService { if (mVoicemailQueryHandler == null) { mVoicemailQueryHandler = new VoicemailQueryHandler(this, getContentResolver()); } - mVoicemailQueryHandler.markNewVoicemailsAsOld(); + mVoicemailQueryHandler.markNewVoicemailsAsOld(intent.getData()); break; case ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS: - Uri voicemailUri = intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI); - DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri); + DefaultVoicemailNotifier.getInstance(this).updateNotification(); break; case ACTION_UPDATE_MISSED_CALL_NOTIFICATIONS: int count = intent.getIntExtra(EXTRA_MISSED_CALL_COUNT, UNKNOWN_MISSED_CALL_COUNT); @@ -184,16 +204,24 @@ public class CallLogNotificationsService extends IntentService { MissedCallNotifier.getInstance(this).updateMissedCallNotification(count, number); updateBadgeCount(this, count); break; + case ACTION_INCOMING_POST_CALL: + String note = intent.getStringExtra(EXTRA_POST_CALL_NOTE); + String phoneNumber = intent.getStringExtra(EXTRA_POST_CALL_NUMBER); + MissedCallNotifier.getInstance(this).insertPostCallNotification(phoneNumber, note); + break; case ACTION_MARK_NEW_MISSED_CALLS_AS_OLD: - CallLogNotificationsHelper.removeMissedCallNotifications(this); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(this, intent.getData()); + TelecomUtil.cancelMissedCallsNotification(this); break; case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION: MissedCallNotifier.getInstance(this) - .callBackFromMissedCall(intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER)); + .callBackFromMissedCall( + intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER), intent.getData()); break; case ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION: MissedCallNotifier.getInstance(this) - .sendSmsFromMissedCall(intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER)); + .sendSmsFromMissedCall( + intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER), intent.getData()); break; default: LogUtil.d("CallLogNotificationsService.onHandleIntent", "could not handle: " + intent); diff --git a/java/com/android/dialer/app/calllog/CallLogReceiver.java b/java/com/android/dialer/app/calllog/CallLogReceiver.java index a781b0887..8fd1502bc 100644 --- a/java/com/android/dialer/app/calllog/CallLogReceiver.java +++ b/java/com/android/dialer/app/calllog/CallLogReceiver.java @@ -38,9 +38,9 @@ public class CallLogReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) { checkVoicemailStatus(context); - CallLogNotificationsService.updateVoicemailNotifications(context, intent.getData()); + CallLogNotificationsService.updateVoicemailNotifications(context); } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - CallLogNotificationsService.updateVoicemailNotifications(context, null); + CallLogNotificationsService.updateVoicemailNotifications(context); } else { LogUtil.w("CallLogReceiver.onReceive", "could not handle: " + intent); } diff --git a/java/com/android/dialer/app/calllog/CallTypeHelper.java b/java/com/android/dialer/app/calllog/CallTypeHelper.java deleted file mode 100644 index f3c27a1ac..000000000 --- a/java/com/android/dialer/app/calllog/CallTypeHelper.java +++ /dev/null @@ -1,136 +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.content.res.Resources; -import com.android.dialer.app.R; -import com.android.dialer.compat.AppCompatConstants; - -/** Helper class to perform operations related to call types. */ -public class CallTypeHelper { - - /** Name used to identify incoming calls. */ - private final CharSequence mIncomingName; - /** Name used to identify incoming calls which were transferred to another device. */ - private final CharSequence mIncomingPulledName; - /** Name used to identify outgoing calls. */ - private final CharSequence mOutgoingName; - /** Name used to identify outgoing calls which were transferred to another device. */ - private final CharSequence mOutgoingPulledName; - /** Name used to identify missed calls. */ - private final CharSequence mMissedName; - /** Name used to identify incoming video calls. */ - private final CharSequence mIncomingVideoName; - /** Name used to identify incoming video calls which were transferred to another device. */ - private final CharSequence mIncomingVideoPulledName; - /** Name used to identify outgoing video calls. */ - private final CharSequence mOutgoingVideoName; - /** Name used to identify outgoing video calls which were transferred to another device. */ - private final CharSequence mOutgoingVideoPulledName; - /** Name used to identify missed video calls. */ - private final CharSequence mMissedVideoName; - /** Name used to identify voicemail calls. */ - private final CharSequence mVoicemailName; - /** Name used to identify rejected calls. */ - private final CharSequence mRejectedName; - /** Name used to identify blocked calls. */ - private final CharSequence mBlockedName; - /** Name used to identify calls which were answered on another device. */ - private final CharSequence mAnsweredElsewhereName; - - public CallTypeHelper(Resources resources) { - // Cache these values so that we do not need to look them up each time. - mIncomingName = resources.getString(R.string.type_incoming); - mIncomingPulledName = resources.getString(R.string.type_incoming_pulled); - mOutgoingName = resources.getString(R.string.type_outgoing); - mOutgoingPulledName = resources.getString(R.string.type_outgoing_pulled); - mMissedName = resources.getString(R.string.type_missed); - mIncomingVideoName = resources.getString(R.string.type_incoming_video); - mIncomingVideoPulledName = resources.getString(R.string.type_incoming_video_pulled); - mOutgoingVideoName = resources.getString(R.string.type_outgoing_video); - mOutgoingVideoPulledName = resources.getString(R.string.type_outgoing_video_pulled); - mMissedVideoName = resources.getString(R.string.type_missed_video); - mVoicemailName = resources.getString(R.string.type_voicemail); - mRejectedName = resources.getString(R.string.type_rejected); - mBlockedName = resources.getString(R.string.type_blocked); - mAnsweredElsewhereName = resources.getString(R.string.type_answered_elsewhere); - } - - public static boolean isMissedCallType(int callType) { - return (callType != AppCompatConstants.CALLS_INCOMING_TYPE - && callType != AppCompatConstants.CALLS_OUTGOING_TYPE - && callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE - && callType != AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE); - } - - /** Returns the text used to represent the given call type. */ - public CharSequence getCallTypeText(int callType, boolean isVideoCall, boolean isPulledCall) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - if (isVideoCall) { - if (isPulledCall) { - return mIncomingVideoPulledName; - } else { - return mIncomingVideoName; - } - } else { - if (isPulledCall) { - return mIncomingPulledName; - } else { - return mIncomingName; - } - } - - case AppCompatConstants.CALLS_OUTGOING_TYPE: - if (isVideoCall) { - if (isPulledCall) { - return mOutgoingVideoPulledName; - } else { - return mOutgoingVideoName; - } - } else { - if (isPulledCall) { - return mOutgoingPulledName; - } else { - return mOutgoingName; - } - } - - case AppCompatConstants.CALLS_MISSED_TYPE: - if (isVideoCall) { - return mMissedVideoName; - } else { - return mMissedName; - } - - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return mVoicemailName; - - case AppCompatConstants.CALLS_REJECTED_TYPE: - return mRejectedName; - - case AppCompatConstants.CALLS_BLOCKED_TYPE: - return mBlockedName; - - case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: - return mAnsweredElsewhereName; - - default: - return mMissedName; - } - } -} diff --git a/java/com/android/dialer/app/calllog/CallTypeIconsView.java b/java/com/android/dialer/app/calllog/CallTypeIconsView.java deleted file mode 100644 index cd5c5460c..000000000 --- a/java/com/android/dialer/app/calllog/CallTypeIconsView.java +++ /dev/null @@ -1,221 +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.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.View; -import com.android.contacts.common.util.BitmapUtil; -import com.android.dialer.app.R; -import com.android.dialer.compat.AppCompatConstants; -import java.util.ArrayList; -import java.util.List; - -/** - * View that draws one or more symbols for different types of calls (missed calls, outgoing etc). - * The symbols are set up horizontally. As this view doesn't create subviews, it is better suited - * for ListView-recycling that a regular LinearLayout using ImageViews. - */ -public class CallTypeIconsView extends View { - - private static Resources sResources; - private List mCallTypes = new ArrayList<>(3); - private boolean mShowVideo = false; - private int mWidth; - private int mHeight; - - public CallTypeIconsView(Context context) { - this(context, null); - } - - public CallTypeIconsView(Context context, AttributeSet attrs) { - super(context, attrs); - if (sResources == null) { - sResources = new Resources(context); - } - } - - public void clear() { - mCallTypes.clear(); - mWidth = 0; - mHeight = 0; - invalidate(); - } - - public void add(int callType) { - mCallTypes.add(callType); - - final Drawable drawable = getCallTypeDrawable(callType); - mWidth += drawable.getIntrinsicWidth() + sResources.iconMargin; - mHeight = Math.max(mHeight, drawable.getIntrinsicHeight()); - invalidate(); - } - - /** - * Determines whether the video call icon will be shown. - * - * @param showVideo True where the video icon should be shown. - */ - public void setShowVideo(boolean showVideo) { - mShowVideo = showVideo; - if (showVideo) { - mWidth += sResources.videoCall.getIntrinsicWidth(); - mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight()); - invalidate(); - } - } - - /** - * Determines if the video icon should be shown. - * - * @return True if the video icon should be shown. - */ - public boolean isVideoShown() { - return mShowVideo; - } - - public int getCount() { - return mCallTypes.size(); - } - - public int getCallType(int index) { - return mCallTypes.get(index); - } - - private Drawable getCallTypeDrawable(int callType) { - switch (callType) { - case AppCompatConstants.CALLS_INCOMING_TYPE: - case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: - return sResources.incoming; - case AppCompatConstants.CALLS_OUTGOING_TYPE: - return sResources.outgoing; - case AppCompatConstants.CALLS_MISSED_TYPE: - return sResources.missed; - case AppCompatConstants.CALLS_VOICEMAIL_TYPE: - return sResources.voicemail; - case AppCompatConstants.CALLS_BLOCKED_TYPE: - return sResources.blocked; - default: - // It is possible for users to end up with calls with unknown call types in their - // call history, possibly due to 3rd party call log implementations (e.g. to - // distinguish between rejected and missed calls). Instead of crashing, just - // assume that all unknown call types are missed calls. - return sResources.missed; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mWidth, mHeight); - } - - @Override - protected void onDraw(Canvas canvas) { - int left = 0; - for (Integer callType : mCallTypes) { - final Drawable drawable = getCallTypeDrawable(callType); - final int right = left + drawable.getIntrinsicWidth(); - drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight()); - drawable.draw(canvas); - left = right + sResources.iconMargin; - } - - // If showing the video call icon, draw it scaled appropriately. - if (mShowVideo) { - final Drawable drawable = sResources.videoCall; - final int right = left + sResources.videoCall.getIntrinsicWidth(); - drawable.setBounds(left, 0, right, sResources.videoCall.getIntrinsicHeight()); - drawable.draw(canvas); - } - } - - private static class Resources { - - // Drawable representing an incoming answered call. - public final Drawable incoming; - - // Drawable respresenting an outgoing call. - public final Drawable outgoing; - - // Drawable representing an incoming missed call. - public final Drawable missed; - - // Drawable representing a voicemail. - public final Drawable voicemail; - - // Drawable representing a blocked call. - public final Drawable blocked; - - // Drawable repesenting a video call. - public final Drawable videoCall; - - /** The margin to use for icons. */ - public final int iconMargin; - - /** - * Configures the call icon drawables. A single white call arrow which points down and left is - * used as a basis for all of the call arrow icons, applying rotation and colors as needed. - * - * @param context The current context. - */ - public Resources(Context context) { - final android.content.res.Resources r = context.getResources(); - - incoming = r.getDrawable(R.drawable.ic_call_arrow); - incoming.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); - - // Create a rotated instance of the call arrow for outgoing calls. - outgoing = BitmapUtil.getRotatedDrawable(r, R.drawable.ic_call_arrow, 180f); - outgoing.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); - - // Need to make a copy of the arrow drawable, otherwise the same instance colored - // above will be recolored here. - missed = r.getDrawable(R.drawable.ic_call_arrow).mutate(); - missed.setColorFilter(r.getColor(R.color.missed_call), PorterDuff.Mode.MULTIPLY); - - voicemail = r.getDrawable(R.drawable.quantum_ic_voicemail_white_18); - voicemail.setColorFilter( - r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); - - blocked = getScaledBitmap(context, R.drawable.ic_block_24dp); - blocked.setColorFilter(r.getColor(R.color.blocked_call), PorterDuff.Mode.MULTIPLY); - - videoCall = getScaledBitmap(context, R.drawable.quantum_ic_videocam_white_24); - videoCall.setColorFilter( - r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); - - iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin); - } - - // Gets the icon, scaled to the height of the call type icons. This helps display all the - // icons to be the same height, while preserving their width aspect ratio. - private Drawable getScaledBitmap(Context context, int resourceId) { - Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resourceId); - int scaledHeight = context.getResources().getDimensionPixelSize(R.dimen.call_type_icon_size); - int scaledWidth = - (int) ((float) icon.getWidth() * ((float) scaledHeight / (float) icon.getHeight())); - Bitmap scaledIcon = Bitmap.createScaledBitmap(icon, scaledWidth, scaledHeight, false); - return new BitmapDrawable(context.getResources(), scaledIcon); - } - } -} diff --git a/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java b/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java index 651a0ccb8..cc1dc4f20 100644 --- a/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/DefaultVoicemailNotifier.java @@ -19,31 +19,33 @@ package com.android.dialer.app.calllog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.ContentUris; 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.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.util.Pair; -import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.Log; 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.CallLogNotificationsHelper.NewCall; +import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; +import com.android.dialer.app.contactinfo.ContactPhotoLoader; import com.android.dialer.app.list.ListsFragment; import com.android.dialer.blocking.FilteredNumbersUtil; -import com.android.dialer.telecom.TelecomUtil; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.notification.NotificationChannelManager.Channel; +import com.android.dialer.phonenumbercache.ContactInfo; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -54,26 +56,23 @@ public class DefaultVoicemailNotifier { public static final String TAG = "VoicemailNotifier"; /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; + static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier"; /** The identifier of the notification of new voicemails. */ - private static final int NOTIFICATION_ID = 1; + private static final int NOTIFICATION_ID = R.id.notification_voicemail; - /** The singleton instance of {@link DefaultVoicemailNotifier}. */ - private static DefaultVoicemailNotifier sInstance; + private final Context context; + private final CallLogNotificationsQueryHelper queryHelper; - private final Context mContext; - - private DefaultVoicemailNotifier(Context context) { - mContext = context; + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + DefaultVoicemailNotifier(Context context, CallLogNotificationsQueryHelper queryHelper) { + this.context = context; + this.queryHelper = queryHelper; } - /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */ + /** Returns an instance of {@link DefaultVoicemailNotifier}. */ public static DefaultVoicemailNotifier getInstance(Context context) { - if (sInstance == null) { - ContentResolver contentResolver = context.getContentResolver(); - sInstance = new DefaultVoicemailNotifier(context); - } - return sInstance; + return new DefaultVoicemailNotifier( + context, CallLogNotificationsQueryHelper.getInstance(context)); } /** @@ -84,34 +83,23 @@ public class DefaultVoicemailNotifier { * *

It is not safe to call this method from the main thread. */ - public void updateNotification(Uri newCallUri) { + public void updateNotification() { // Lookup the list of new voicemails to include in the notification. - // TODO: Move this into a service, to avoid holding the receiver up. - final List newCalls = - CallLogNotificationsHelper.getInstance(mContext).getNewVoicemails(); + final List newCalls = queryHelper.getNewVoicemails(); if (newCalls == null) { // Query failed, just return. return; } - if (newCalls.isEmpty()) { - // No voicemails to notify about: clear the notification. - getNotificationManager().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - return; - } - - Resources resources = mContext.getResources(); + 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 names = new ArrayMap<>(); - - // Determine the call corresponding to the new voicemail we have to notify about. - NewCall callToNotify = null; + final Map contactInfos = new ArrayMap<>(); // Iterate over the new voicemails to determine all the information above. Iterator itr = newCalls.iterator(); @@ -120,95 +108,64 @@ public class DefaultVoicemailNotifier { // Skip notifying for numbers which are blocked. if (FilteredNumbersUtil.shouldBlockVoicemail( - mContext, newCall.number, newCall.countryIso, newCall.dateMs)) { + context, newCall.number, newCall.countryIso, newCall.dateMs)) { itr.remove(); // Delete the voicemail. - mContext.getContentResolver().delete(newCall.voicemailUri, null, null); + context.getContentResolver().delete(newCall.voicemailUri, null, null); continue; } // Check if we already know the name associated with this number. - String name = names.get(newCall.number); - if (name == null) { - name = - CallLogNotificationsHelper.getInstance(mContext) - .getName(newCall.number, newCall.numberPresentation, newCall.countryIso); - names.put(newCall.number, name); + 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 = name; + callers = contactInfo.name; } else { callers = - resources.getString(R.string.notification_voicemail_callers_list, callers, name); + resources.getString( + R.string.notification_voicemail_callers_list, callers, contactInfo.name); } } - // Check if this is the new call we need to notify about. - if (newCallUri != null - && newCall.voicemailUri != null - && ContentUris.parseId(newCallUri) == ContentUris.parseId(newCall.voicemailUri)) { - callToNotify = newCall; - } } - // All the potential new voicemails have been removed, e.g. if they were spam. if (newCalls.isEmpty()) { + // No voicemails to notify about: clear the notification. + CallLogNotificationsService.markNewVoicemailsAsOld(context, null); return; } - // If there is only one voicemail, set its transcription as the "long text". - String transcription = null; - if (newCalls.size() == 1) { - transcription = newCalls.get(0).transcription; - } - - if (newCallUri != null && callToNotify == null) { - Log.e(TAG, "The new call could not be found in the call log: " + newCallUri); - } - - // Determine the title of the notification and the icon for it. - final String title = - resources.getQuantityString( - R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size()); - // TODO: Use the photo of contact if all calls are from the same person. - final int icon = android.R.drawable.stat_notify_voicemail; - - Pair info = getNotificationInfo(callToNotify); - - Notification.Builder notificationBuilder = - new Notification.Builder(mContext) - .setSmallIcon(icon) - .setContentTitle(title) + Notification.Builder groupSummary = + createNotificationBuilder() + .setContentTitle( + resources.getQuantityString( + R.plurals.notification_voicemail_title, newCalls.size(), newCalls.size())) .setContentText(callers) - .setColor(resources.getColor(R.color.dialer_theme_color)) - .setSound(info.first) - .setDefaults(info.second) - .setDeleteIntent(createMarkNewVoicemailsAsOldIntent()) - .setAutoCancel(true); - - if (!TextUtils.isEmpty(transcription)) { - notificationBuilder.setStyle(new Notification.BigTextStyle().bigText(transcription)); + .setDeleteIntent(createMarkNewVoicemailsAsOldIntent(null)) + .setGroupSummary(true) + .setContentIntent(newVoicemailIntent(null)); + + NotificationChannelManager.applyChannel( + groupSummary, + context, + Channel.VOICEMAIL, + PhoneAccountHandles.getAccount(context, newCalls.get(0))); + + LogUtil.i(TAG, "Creating voicemail notification"); + getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, groupSummary.build()); + + for (NewCall voicemail : newCalls) { + getNotificationManager() + .notify( + voicemail.voicemailUri.toString(), + NOTIFICATION_ID, + createNotificationForVoicemail(voicemail, contactInfos)); } - - // Determine the intent to fire when the notification is clicked on. - final Intent contentIntent; - // Open the call log. - contentIntent = DialtactsActivity.getShowTabIntent(mContext, ListsFragment.TAB_INDEX_VOICEMAIL); - contentIntent.putExtra(DialtactsActivity.EXTRA_CLEAR_NEW_VOICEMAILS, true); - notificationBuilder.setContentIntent( - PendingIntent.getActivity(mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)); - - // The text to show in the ticker, describing the new event. - if (callToNotify != null) { - CharSequence msg = - ContactDisplayUtils.getTtsSpannedPhoneNumber( - resources, - R.string.notification_new_voicemail_ticker, - names.get(callToNotify.number)); - notificationBuilder.setTicker(msg); - } - Log.i(TAG, "Creating voicemail notification"); - getNotificationManager().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notificationBuilder.build()); } /** @@ -216,30 +173,15 @@ public class DefaultVoicemailNotifier { * for the given call. */ private Pair getNotificationInfo(@Nullable NewCall callToNotify) { - Log.v(TAG, "getNotificationInfo"); + LogUtil.v(TAG, "getNotificationInfo"); if (callToNotify == null) { - Log.i(TAG, "callToNotify == null"); + LogUtil.i(TAG, "callToNotify == null"); return new Pair<>(null, 0); } - PhoneAccountHandle accountHandle; - if (callToNotify.accountComponentName == null || callToNotify.accountId == null) { - Log.v(TAG, "accountComponentName == null || callToNotify.accountId == null"); - accountHandle = TelecomUtil.getDefaultOutgoingPhoneAccount(mContext, PhoneAccount.SCHEME_TEL); - if (accountHandle == null) { - Log.i(TAG, "No default phone account found, using default notification ringtone"); - return new Pair<>(null, Notification.DEFAULT_ALL); - } - - } else { - accountHandle = - new PhoneAccountHandle( - ComponentName.unflattenFromString(callToNotify.accountComponentName), - callToNotify.accountId); - } - if (accountHandle.getComponentName() != null) { - Log.v(TAG, "PhoneAccountHandle.ComponentInfo:" + accountHandle.getComponentName()); - } else { - Log.i(TAG, "PhoneAccountHandle.ComponentInfo: null"); + 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), @@ -257,17 +199,79 @@ public class DefaultVoicemailNotifier { } /** Creates a pending intent that marks all new voicemails as old. */ - private PendingIntent createMarkNewVoicemailsAsOldIntent() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createMarkNewVoicemailsAsOldIntent(@Nullable Uri voicemailUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); + intent.setData(voicemailUri); + return PendingIntent.getService(context, 0, intent, 0); } private NotificationManager getNotificationManager() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } private TelephonyManager getTelephonyManager() { - return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + } + + private Notification createNotificationForVoicemail( + @NonNull NewCall voicemail, @NonNull Map contactInfos) { + Pair 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) + .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)) { + notificationBuilder.setStyle( + new Notification.BigTextStyle().bigText(voicemail.transcription)); + } + notificationBuilder.setContentIntent(newVoicemailIntent(voicemail)); + + 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(NOTIFICATION_TAG) + .setOnlyAlertOnce(true) + .setAutoCancel(true); + } + + private PendingIntent newVoicemailIntent(@Nullable NewCall voicemail) { + Intent intent = DialtactsActivity.getShowTabIntent(context, ListsFragment.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); } } diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java index 879ac353d..c53e3ec5e 100644 --- a/java/com/android/dialer/app/calllog/IntentProvider.java +++ b/java/com/android/dialer/app/calllog/IntentProvider.java @@ -16,7 +16,6 @@ package com.android.dialer.app.calllog; -import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -25,10 +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.app.CallDetailActivity; +import com.android.dialer.callcomposer.nano.CallComposerContact; +import com.android.dialer.calldetails.CallDetailsActivity; +import com.android.dialer.calldetails.nano.CallDetailsEntries; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.IntentUtil; import java.util.ArrayList; @@ -97,29 +97,16 @@ public abstract class IntentProvider { /** * Retrieves the call details intent provider for an entry in the call log. * - * @param id The call ID of the first call in the call group. - * @param extraIds The call ID of the other calls grouped together with the call. - * @param voicemailUri If call log entry is for a voicemail, the voicemail URI. + * @param callDetailsEntries The call details of the other calls grouped together with the call. + * @param contact The contact with which this call details intent pertains to. * @return The call details intent provider. */ public static IntentProvider getCallDetailIntentProvider( - final long id, final long[] extraIds, final String voicemailUri) { + CallDetailsEntries callDetailsEntries, CallComposerContact contact) { return new IntentProvider() { @Override public Intent getIntent(Context context) { - Intent intent = new Intent(context, CallDetailActivity.class); - // Check if the first item is a voicemail. - if (voicemailUri != null) { - intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, Uri.parse(voicemailUri)); - } - - if (extraIds != null && extraIds.length > 0) { - intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, extraIds); - } else { - // If there is a single item, use the direct URI for it. - intent.setData(ContentUris.withAppendedId(TelecomUtil.getCallLogUri(context), id)); - } - return intent; + return CallDetailsActivity.newInstance(context, callDetailsEntries, contact); } }; } diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java index 2fa3dae65..5b5661615 100644 --- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java +++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java @@ -16,16 +16,20 @@ 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.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.os.AsyncTask; +import android.graphics.drawable.Icon; +import android.net.Uri; import android.provider.CallLog.Calls; +import android.service.notification.StatusBarNotification; +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.UserManagerCompat; import android.text.BidiFormatter; import android.text.TextDirectionHeuristics; @@ -34,109 +38,117 @@ import com.android.contacts.common.ContactsUtils; import com.android.contacts.common.compat.PhoneNumberUtilsCompat; import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; -import com.android.dialer.app.calllog.CallLogNotificationsHelper.NewCall; +import com.android.dialer.app.calllog.CallLogNotificationsQueryHelper.NewCall; import com.android.dialer.app.contactinfo.ContactPhotoLoader; import com.android.dialer.app.list.ListsFragment; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; -import com.android.dialer.common.ConfigProviderBindings; import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.NotificationChannelManager; +import com.android.dialer.notification.NotificationChannelManager.Channel; import com.android.dialer.phonenumbercache.ContactInfo; import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import com.android.dialer.telecom.TelecomUtil; 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 { /** The tag used to identify notifications from this class. */ - private static final String NOTIFICATION_TAG = "MissedCallNotifier"; + static final String NOTIFICATION_TAG = "MissedCallNotifier"; /** The identifier of the notification of new missed calls. */ - private static final int NOTIFICATION_ID = 1; + private static final int NOTIFICATION_ID = R.id.notification_missed_call; - private static MissedCallNotifier sInstance; - private Context mContext; - private CallLogNotificationsHelper mCalllogNotificationsHelper; + private final Context context; + private final CallLogNotificationsQueryHelper callLogNotificationsQueryHelper; @VisibleForTesting - MissedCallNotifier(Context context, CallLogNotificationsHelper callLogNotificationsHelper) { - mContext = context; - mCalllogNotificationsHelper = callLogNotificationsHelper; + MissedCallNotifier( + Context context, CallLogNotificationsQueryHelper callLogNotificationsQueryHelper) { + this.context = context; + this.callLogNotificationsQueryHelper = callLogNotificationsQueryHelper; } - /** Returns the singleton instance of the {@link MissedCallNotifier}. */ + /** Returns an instance of {@link MissedCallNotifier}. */ public static MissedCallNotifier getInstance(Context context) { - if (sInstance == null) { - CallLogNotificationsHelper callLogNotificationsHelper = - CallLogNotificationsHelper.getInstance(context); - sInstance = new MissedCallNotifier(context, callLogNotificationsHelper); - } - return sInstance; + CallLogNotificationsQueryHelper callLogNotificationsQueryHelper = + CallLogNotificationsQueryHelper.getInstance(context); + return new MissedCallNotifier(context, callLogNotificationsQueryHelper); } /** - * Creates a missed call notification with a post call message if there are no existing missed - * calls. + * Update missed call notifications from the call log. Accepts default information in case call + * log cannot be accessed. + * + * @param count the number of missed calls to display if call log cannot be accessed. May be + * {@link CallLogNotificationsService#UNKNOWN_MISSED_CALL_COUNT} if unknown. + * @param number the phone number of the most recent call to display if the call log cannot be + * accessed. May be null if unknown. */ - public void createPostCallMessageNotification(String number, String message) { - int count = CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT; - if (ConfigProviderBindings.get(mContext).getBoolean("enable_call_compose", false)) { - updateMissedCallNotification(count, number, message); - } else { - updateMissedCallNotification(count, number, null); - } - } - - /** Creates a missed call notification. */ - public void updateMissedCallNotification(int count, String number) { - updateMissedCallNotification(count, number, null); - } - - private void updateMissedCallNotification( - int count, String number, @Nullable String postCallMessage) { + @WorkerThread + public void updateMissedCallNotification(int count, @Nullable String number) { final int titleResId; CharSequence expandedText; // The text in the notification's line 1 and 2. - final List newCalls = mCalllogNotificationsHelper.getNewMissedCalls(); + List newCalls = callLogNotificationsQueryHelper.getNewMissedCalls(); - if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { - if (newCalls == null) { - // If the intent did not contain a count, and we are unable to get a count from the - // call log, then no notification can be shown. - return; + if ((newCalls != null && newCalls.isEmpty()) || count == 0) { + // No calls to notify about: clear the notification. + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, null); + return; + } + + if (newCalls != null) { + if (count != CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT + && count != newCalls.size()) { + LogUtil.w( + "MissedCallNotifier.updateMissedCallNotification", + "Call count does not match call log count." + + " count: " + + count + + " newCalls.size(): " + + newCalls.size()); } count = newCalls.size(); } - if (count == 0) { - // No voicemails to notify about: clear the notification. - clearMissedCalls(); + if (count == CallLogNotificationsService.UNKNOWN_MISSED_CALL_COUNT) { + // If the intent did not contain a count, and we are unable to get a count from the + // call log, then no notification can be shown. return; } - // The call log has been updated, use that information preferentially. - boolean useCallLog = newCalls != null && newCalls.size() == count; - NewCall newestCall = useCallLog ? newCalls.get(0) : null; - long timeMs = useCallLog ? newestCall.dateMs : System.currentTimeMillis(); - String missedNumber = useCallLog ? newestCall.number : number; + Notification.Builder groupSummary = createNotificationBuilder(); + boolean useCallList = newCalls != null; - Notification.Builder builder = new Notification.Builder(mContext); - // Display the first line of the notification: - // 1 missed call: - // More than 1 missed call: + "missed calls" if (count == 1) { + NewCall call = + useCallList + ? newCalls.get(0) + : new NewCall( + null, + null, + number, + Calls.PRESENTATION_ALLOWED, + null, + null, + null, + null, + System.currentTimeMillis()); + //TODO: look up caller ID that is not in contacts. ContactInfo contactInfo = - mCalllogNotificationsHelper.getContactInfo( - missedNumber, - useCallLog ? newestCall.numberPresentation : Calls.PRESENTATION_ALLOWED, - useCallLog ? newestCall.countryIso : null); - + callLogNotificationsQueryHelper.getContactInfo( + call.number, call.numberPresentation, call.countryIso); titleResId = contactInfo.userType == ContactsUtils.USER_TYPE_WORK ? R.string.notification_missedWorkCallTitle : R.string.notification_missedCallTitle; + if (TextUtils.equals(contactInfo.name, contactInfo.formattedNumber) || TextUtils.equals(contactInfo.name, contactInfo.number)) { expandedText = @@ -147,134 +159,195 @@ public class MissedCallNotifier { expandedText = contactInfo.name; } - if (!TextUtils.isEmpty(postCallMessage)) { - // Ex. "John Doe: Hey dude" - expandedText = - mContext.getString( - R.string.post_call_notification_message, expandedText, postCallMessage); - } - ContactPhotoLoader loader = new ContactPhotoLoader(mContext, contactInfo); + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); Bitmap photoIcon = loader.loadPhotoIcon(); if (photoIcon != null) { - builder.setLargeIcon(photoIcon); + groupSummary.setLargeIcon(photoIcon); } } else { titleResId = R.string.notification_missedCallsTitle; - expandedText = mContext.getString(R.string.notification_missedCallsMsg, count); + expandedText = context.getString(R.string.notification_missedCallsMsg, count); } // Create a public viewable version of the notification, suitable for display when sensitive // notification content is hidden. - Notification.Builder publicBuilder = new Notification.Builder(mContext); - publicBuilder - .setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - // Show "Phone" for notification title. - .setContentTitle(mContext.getText(R.string.userCallActivityLabel)) - // Notification details shows that there are missed call(s), but does not reveal - // the missed caller information. - .setContentText(mContext.getText(titleResId)) + Notification.Builder publicSummaryBuilder = createNotificationBuilder(); + publicSummaryBuilder + .setContentTitle(context.getText(titleResId)) .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setShowWhen(true) - .setDeleteIntent(createClearMissedCallsPendingIntent()); + .setDeleteIntent(createClearMissedCallsPendingIntent(null)); + // Create the notification summary suitable for display when sensitive information is showing. + groupSummary + .setContentTitle(context.getText(titleResId)) + .setContentText(expandedText) + .setContentIntent(createCallLogPendingIntent()) + .setDeleteIntent(createClearMissedCallsPendingIntent(null)) + .setGroupSummary(useCallList) + .setOnlyAlertOnce(useCallList) + .setPublicVersion(publicSummaryBuilder.build()); + + NotificationChannelManager.applyChannel( + groupSummary, + context, + Channel.MISSED_CALL, + PhoneAccountHandles.getAccount(context, useCallList ? newCalls.get(0) : null)); + + Notification notification = groupSummary.build(); + configureLedOnNotification(notification); + + LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); + getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + + if (useCallList) { + // Do not repost active notifications to prevent erasing post call notes. + NotificationManager manager = getNotificationMgr(); + Set activeTags = new HashSet<>(); + for (StatusBarNotification activeNotification : manager.getActiveNotifications()) { + activeTags.add(activeNotification.getTag()); + } + + for (NewCall call : newCalls) { + String callTag = call.callsUri.toString(); + if (!activeTags.contains(callTag)) { + manager.notify(callTag, NOTIFICATION_ID, getNotificationForCall(call, null)); + } + } + } + } + + public void insertPostCallNotification(@NonNull String number, @NonNull String note) { + List 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)); + break; + } + } + } + } + + private Notification getNotificationForCall( + @NonNull NewCall call, @Nullable String postCallMessage) { + ContactInfo contactInfo = + callLogNotificationsQueryHelper.getContactInfo( + call.number, call.numberPresentation, call.countryIso); + + // Create a public viewable version of the notification, suitable for display when sensitive + // notification content is hidden. + int titleResId = + contactInfo.userType == ContactsUtils.USER_TYPE_WORK + ? R.string.notification_missedWorkCallTitle + : R.string.notification_missedCallTitle; + Notification.Builder publicBuilder = + createNotificationBuilder(call).setContentTitle(context.getText(titleResId)); + + Notification.Builder builder = createNotificationBuilder(call); + CharSequence expandedText; + if (TextUtils.equals(contactInfo.name, contactInfo.formattedNumber) + || TextUtils.equals(contactInfo.name, contactInfo.number)) { + expandedText = + PhoneNumberUtilsCompat.createTtsSpannable( + BidiFormatter.getInstance() + .unicodeWrap(contactInfo.name, TextDirectionHeuristics.LTR)); + } else { + expandedText = contactInfo.name; + } + + if (postCallMessage != null) { + expandedText = + context.getString(R.string.post_call_notification_message, expandedText, postCallMessage); + } + + ContactPhotoLoader loader = new ContactPhotoLoader(context, contactInfo); + Bitmap photoIcon = loader.loadPhotoIcon(); + if (photoIcon != null) { + builder.setLargeIcon(photoIcon); + } // Create the notification suitable for display when sensitive information is showing. builder - .setSmallIcon(android.R.drawable.stat_notify_missed_call) - .setColor(mContext.getResources().getColor(R.color.dialer_theme_color)) - .setContentTitle(mContext.getText(titleResId)) + .setContentTitle(context.getText(titleResId)) .setContentText(expandedText) - .setContentIntent(createCallLogPendingIntent()) - .setAutoCancel(true) - .setWhen(timeMs) - .setShowWhen(true) - .setDefaults(Notification.DEFAULT_VIBRATE) - .setDeleteIntent(createClearMissedCallsPendingIntent()) // Include a public version of the notification to be shown when the missed call // notification is shown on the user's lock screen and they have chosen to hide // sensitive notification information. .setPublicVersion(publicBuilder.build()); - // Add additional actions when there is only 1 missed call and the user isn't locked - if (UserManagerCompat.isUserUnlocked(mContext) && count == 1) { - if (!TextUtils.isEmpty(missedNumber) - && !TextUtils.equals(missedNumber, mContext.getString(R.string.handle_restricted))) { + // Add additional actions when the user isn't locked + if (UserManagerCompat.isUserUnlocked(context)) { + if (!TextUtils.isEmpty(call.number) + && !TextUtils.equals(call.number, context.getString(R.string.handle_restricted))) { builder.addAction( - R.drawable.ic_phone_24dp, - mContext.getString(R.string.notification_missedCall_call_back), - createCallBackPendingIntent(missedNumber)); + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_phone_24dp), + context.getString(R.string.notification_missedCall_call_back), + createCallBackPendingIntent(call.number, call.callsUri)) + .build()); - if (!PhoneNumberHelper.isUriNumber(missedNumber)) { + if (!PhoneNumberHelper.isUriNumber(call.number)) { builder.addAction( - R.drawable.ic_message_24dp, - mContext.getString(R.string.notification_missedCall_message), - createSendSmsFromNotificationPendingIntent(missedNumber)); + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_message_24dp), + context.getString(R.string.notification_missedCall_message), + createSendSmsFromNotificationPendingIntent(call.number, call.callsUri)) + .build()); } } } Notification notification = builder.build(); configureLedOnNotification(notification); + return notification; + } - LogUtil.i("MissedCallNotifier.updateMissedCallNotification", "adding missed call notification"); - getNotificationMgr().notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + private Notification.Builder createNotificationBuilder() { + return new Notification.Builder(context) + .setGroup(NOTIFICATION_TAG) + .setSmallIcon(android.R.drawable.stat_notify_missed_call) + .setColor(context.getResources().getColor(R.color.dialer_theme_color, null)) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setShowWhen(true) + .setDefaults(Notification.DEFAULT_VIBRATE); } - private void clearMissedCalls() { - AsyncTask.execute( - new Runnable() { - @Override - public void run() { - // 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(mContext)) { - 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 { - mContext - .getContentResolver() - .update( - Calls.CONTENT_URI, - values, - where.toString(), - new String[] {Integer.toString(Calls.MISSED_TYPE)}); - } catch (IllegalArgumentException e) { - LogUtil.e( - "MissedCallNotifier.clearMissedCalls", - "contacts provider update command failed", - e); - } - } - getNotificationMgr().cancel(NOTIFICATION_TAG, NOTIFICATION_ID); - } - }); + private Notification.Builder createNotificationBuilder(@NonNull NewCall call) { + Builder builder = + createNotificationBuilder() + .setWhen(call.dateMs) + .setDeleteIntent(createClearMissedCallsPendingIntent(call.callsUri)) + .setContentIntent(createCallLogPendingIntent(call.callsUri)); + + NotificationChannelManager.applyChannel( + builder, context, Channel.MISSED_CALL, PhoneAccountHandles.getAccount(context, call)); + return builder; } /** Trigger an intent to make a call from a missed call number. */ - public void callBackFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); + @WorkerThread + public void callBackFromMissedCall(String number, Uri callUri) { + closeSystemDialogs(context); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri); + TelecomUtil.cancelMissedCallsNotification(context); DialerUtils.startActivityWithErrorToast( - mContext, + context, new CallIntentBuilder(number, CallInitiationType.Type.MISSED_CALL_NOTIFICATION) .build() .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } /** Trigger an intent to send an sms from a missed call number. */ - public void sendSmsFromMissedCall(String number) { - closeSystemDialogs(mContext); - CallLogNotificationsHelper.removeMissedCallNotifications(mContext); + @WorkerThread + public void sendSmsFromMissedCall(String number, Uri callUri) { + closeSystemDialogs(context); + CallLogNotificationsQueryHelper.removeMissedCallNotifications(context, callUri); + TelecomUtil.cancelMissedCallsNotification(context); DialerUtils.startActivityWithErrorToast( - mContext, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + context, IntentUtil.getSendSmsIntent(number).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } /** @@ -283,34 +356,50 @@ public class MissedCallNotifier { * @return The pending intent. */ private PendingIntent createCallLogPendingIntent() { + return createCallLogPendingIntent(null); + } + + /** + * Creates a new pending intent that sends the user to the call log. + * + * @return The pending intent. + * @param callUri Uri of the call to jump to. May be null + */ + private PendingIntent createCallLogPendingIntent(@Nullable Uri callUri) { Intent contentIntent = - DialtactsActivity.getShowTabIntent(mContext, ListsFragment.TAB_INDEX_HISTORY); - return PendingIntent.getActivity(mContext, 0, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT); + DialtactsActivity.getShowTabIntent(context, ListsFragment.TAB_INDEX_HISTORY); + // TODO (b/35486204): scroll to call + contentIntent.setData(callUri); + 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() { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createClearMissedCallsPendingIntent(@Nullable Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_MISSED_CALLS_AS_OLD); - return PendingIntent.getService(mContext, 0, intent, 0); + intent.setData(callUri); + return PendingIntent.getService(context, 0, intent, 0); } - private PendingIntent createCallBackPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + 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); intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); + intent.setData(callUri); // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } - private PendingIntent createSendSmsFromNotificationPendingIntent(String number) { - Intent intent = new Intent(mContext, CallLogNotificationsService.class); + private PendingIntent createSendSmsFromNotificationPendingIntent( + String number, @NonNull Uri callUri) { + Intent intent = new Intent(context, CallLogNotificationsService.class); intent.setAction(CallLogNotificationsService.ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION); intent.putExtra(CallLogNotificationsService.EXTRA_MISSED_CALL_NUMBER, number); + intent.setData(callUri); // Use FLAG_UPDATE_CURRENT to make sure any previous pending intent is updated with the new // extra. - return PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } /** Configures a notification to emit the blinky notification light. */ @@ -325,6 +414,6 @@ public class MissedCallNotifier { } private NotificationManager getNotificationMgr() { - return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + 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 new file mode 100644 index 000000000..6d51b853c --- /dev/null +++ b/java/com/android/dialer/app/calllog/PhoneAccountHandles.java @@ -0,0 +1,41 @@ +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; + +/** 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) { + 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/PhoneAccountUtils.java b/java/com/android/dialer/app/calllog/PhoneAccountUtils.java deleted file mode 100644 index c6d94d341..000000000 --- a/java/com/android/dialer/app/calllog/PhoneAccountUtils.java +++ /dev/null @@ -1,104 +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; - -import android.content.ComponentName; -import android.content.Context; -import android.support.annotation.Nullable; -import android.telecom.PhoneAccount; -import android.telecom.PhoneAccountHandle; -import android.text.TextUtils; -import com.android.dialer.telecom.TelecomUtil; -import java.util.ArrayList; -import java.util.List; - -/** Methods to help extract {@code PhoneAccount} information from database and Telecomm sources. */ -public class PhoneAccountUtils { - - /** Return a list of phone accounts that are subscription/SIM accounts. */ - public static List getSubscriptionPhoneAccounts(Context context) { - List subscriptionAccountHandles = new ArrayList(); - final List accountHandles = - TelecomUtil.getCallCapablePhoneAccounts(context); - for (PhoneAccountHandle accountHandle : accountHandles) { - PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { - subscriptionAccountHandles.add(accountHandle); - } - } - return subscriptionAccountHandles; - } - - /** Compose PhoneAccount object from component name and account id. */ - @Nullable - public static PhoneAccountHandle getAccount( - @Nullable String componentString, @Nullable String accountId) { - if (TextUtils.isEmpty(componentString) || TextUtils.isEmpty(accountId)) { - return null; - } - final ComponentName componentName = ComponentName.unflattenFromString(componentString); - if (componentName == null) { - return null; - } - return new PhoneAccountHandle(componentName, accountId); - } - - /** Extract account label from PhoneAccount object. */ - @Nullable - public static String getAccountLabel( - Context context, @Nullable PhoneAccountHandle accountHandle) { - PhoneAccount account = getAccountOrNull(context, accountHandle); - if (account != null && account.getLabel() != null) { - return account.getLabel().toString(); - } - return null; - } - - /** Extract account color from PhoneAccount object. */ - public static int getAccountColor(Context context, @Nullable PhoneAccountHandle accountHandle) { - final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - - // For single-sim devices the PhoneAccount will be NO_HIGHLIGHT_COLOR by default, so it is - // safe to always use the account highlight color. - return account == null ? PhoneAccount.NO_HIGHLIGHT_COLOR : account.getHighlightColor(); - } - - /** - * Determine whether a phone account supports call subjects. - * - * @return {@code true} if call subjects are supported, {@code false} otherwise. - */ - public static boolean getAccountSupportsCallSubject( - Context context, @Nullable PhoneAccountHandle accountHandle) { - final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); - - return account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); - } - - /** - * Retrieve the account metadata, but if the account does not exist or the device has only a - * single registered and enabled account, return null. - */ - @Nullable - private static PhoneAccount getAccountOrNull( - Context context, @Nullable PhoneAccountHandle accountHandle) { - if (TelecomUtil.getCallCapablePhoneAccounts(context).size() <= 1) { - return null; - } - return TelecomUtil.getPhoneAccount(context, accountHandle); - } -} diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java index b18270bb3..acbccb39f 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsHelper.java @@ -27,9 +27,10 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.view.View; import android.widget.TextView; -import com.android.dialer.app.PhoneCallDetails; import com.android.dialer.app.R; import com.android.dialer.app.calllog.calllogcache.CallLogCache; +import com.android.dialer.calllogutils.PhoneCallDetails; +import com.android.dialer.oem.MotorolaUtils; import com.android.dialer.phonenumberutil.PhoneNumberHelper; import com.android.dialer.util.DialerUtils; import java.util.ArrayList; @@ -84,6 +85,8 @@ public class PhoneCallDetailsHelper { // Show the video icon if the call had video enabled. views.callTypeIcons.setShowVideo( (details.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO); + views.callTypeIcons.setShowHd( + MotorolaUtils.shouldShowHdIconInCallLog(mContext, details.features)); views.callTypeIcons.requestLayout(); views.callTypeIcons.setVisibility(View.VISIBLE); diff --git a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java index 476996826..e2e27a179 100644 --- a/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java +++ b/java/com/android/dialer/app/calllog/PhoneCallDetailsViews.java @@ -20,6 +20,7 @@ import android.content.Context; import android.view.View; import android.widget.TextView; import com.android.dialer.app.R; +import com.android.dialer.calllogutils.CallTypeIconsView; /** Encapsulates the views that are used to display the details of a phone call in the call log. */ public final class PhoneCallDetailsViews { diff --git a/java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java b/java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java deleted file mode 100644 index 410d4cc37..000000000 --- a/java/com/android/dialer/app/calllog/PhoneNumberDisplayUtil.java +++ /dev/null @@ -1,85 +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.content.Context; -import android.provider.CallLog.Calls; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.text.TextUtils; -import com.android.contacts.common.compat.PhoneNumberUtilsCompat; -import com.android.dialer.app.R; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; - -/** Helper for formatting and managing the display of phone numbers. */ -public class PhoneNumberDisplayUtil { - - /** Returns the string to display for the given phone number if there is no matching contact. */ - /* package */ - static CharSequence getDisplayName( - Context context, CharSequence number, int presentation, boolean isVoicemail) { - if (presentation == Calls.PRESENTATION_UNKNOWN) { - return context.getResources().getString(R.string.unknown); - } - if (presentation == Calls.PRESENTATION_RESTRICTED) { - return PhoneNumberHelper.getDisplayNameForRestrictedNumber(context); - } - if (presentation == Calls.PRESENTATION_PAYPHONE) { - return context.getResources().getString(R.string.payphone); - } - if (isVoicemail) { - return context.getResources().getString(R.string.voicemail); - } - if (PhoneNumberHelper.isLegacyUnknownNumbers(number)) { - return context.getResources().getString(R.string.unknown); - } - return ""; - } - - /** - * Returns the string to display for the given phone number. - * - * @param number the number to display - * @param formattedNumber the formatted number if available, may be null - */ - public static CharSequence getDisplayNumber( - Context context, - CharSequence number, - int presentation, - CharSequence formattedNumber, - CharSequence postDialDigits, - boolean isVoicemail) { - final CharSequence displayName = getDisplayName(context, number, presentation, isVoicemail); - if (!TextUtils.isEmpty(displayName)) { - return getTtsSpannableLtrNumber(displayName); - } - - if (!TextUtils.isEmpty(formattedNumber)) { - return getTtsSpannableLtrNumber(formattedNumber); - } else if (!TextUtils.isEmpty(number)) { - return getTtsSpannableLtrNumber(number.toString() + postDialDigits); - } else { - return context.getResources().getString(R.string.unknown); - } - } - - /** Returns number annotated as phone number in LTR direction. */ - public static CharSequence getTtsSpannableLtrNumber(CharSequence number) { - return PhoneNumberUtilsCompat.createTtsSpannable( - BidiFormatter.getInstance().unicodeWrap(number.toString(), TextDirectionHeuristics.LTR)); - } -} diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java b/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java index e539ceef6..6f101f580 100644 --- a/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java +++ b/java/com/android/dialer/app/calllog/VisualVoicemailCallLogFragment.java @@ -40,10 +40,13 @@ public class VisualVoicemailCallLogFragment extends CallLogFragment { private VoicemailErrorManager mVoicemailAlertManager; + public VisualVoicemailCallLogFragment() { + super(CallLog.Calls.VOICEMAIL_TYPE); + } + @Override public void onCreate(Bundle state) { super.onCreate(state); - mCallTypeFilter = CallLog.Calls.VOICEMAIL_TYPE; mVoicemailPlaybackPresenter = VoicemailPlaybackPresenter.getInstance(getActivity(), state); getActivity() .getContentResolver() diff --git a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java index d6d8354ec..e73684e70 100644 --- a/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java +++ b/java/com/android/dialer/app/calllog/VoicemailQueryHandler.java @@ -15,13 +15,18 @@ */ package com.android.dialer.app.calllog; +import android.app.NotificationManager; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.provider.CallLog.Calls; -import android.util.Log; +import android.support.annotation.Nullable; +import com.android.dialer.app.R; +import com.android.dialer.common.LogUtil; +import com.android.dialer.notification.GroupedNotificationUtil; /** Handles asynchronous queries to the call log for voicemail. */ public class VoicemailQueryHandler extends AsyncQueryHandler { @@ -39,7 +44,7 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { } /** Updates all new voicemails to mark them as old. */ - public void markNewVoicemailsAsOld() { + public void markNewVoicemailsAsOld(@Nullable Uri voicemailUri) { // Mark all "new" voicemails as not new anymore. StringBuilder where = new StringBuilder(); where.append(Calls.NEW); @@ -47,6 +52,10 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { where.append(Calls.TYPE); where.append(" = ?"); + if (voicemailUri != null) { + where.append(" AND ").append(Calls.VOICEMAIL_URI).append(" = ?"); + } + ContentValues values = new ContentValues(1); values.put(Calls.NEW, "0"); @@ -56,7 +65,15 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { Calls.CONTENT_URI_WITH_VOICEMAIL, values, where.toString(), - new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}); + 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_voicemail, + DefaultVoicemailNotifier.NOTIFICATION_TAG); } @Override @@ -67,7 +84,7 @@ public class VoicemailQueryHandler extends AsyncQueryHandler { serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_VOICEMAIL_NOTIFICATIONS); mContext.startService(serviceIntent); } else { - Log.w(TAG, "Unknown update completed: ignoring: " + token); + LogUtil.w(TAG, "Unknown update completed: ignoring: " + token); } } } diff --git a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java b/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java index c342b7e3b..039998780 100644 --- a/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java +++ b/java/com/android/dialer/app/calllog/calllogcache/CallLogCacheLollipopMr1.java @@ -20,10 +20,10 @@ 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.app.calllog.PhoneAccountUtils; +import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -45,9 +45,9 @@ class CallLogCacheLollipopMr1 extends CallLogCache { final Map, Boolean> mVoicemailQueryCache = new ConcurrentHashMap<>(); - private final Map mPhoneAccountLabelCache = new HashMap<>(); - private final Map mPhoneAccountColorCache = new HashMap<>(); - private final Map mPhoneAccountCallWithNoteCache = new HashMap<>(); + private final Map mPhoneAccountLabelCache = new ArrayMap<>(); + private final Map mPhoneAccountColorCache = new ArrayMap<>(); + private final Map mPhoneAccountCallWithNoteCache = new ArrayMap<>(); /* package */ CallLogCacheLollipopMr1(Context context) { super(context); diff --git a/java/com/android/dialer/app/contactinfo/ContactInfoCache.java b/java/com/android/dialer/app/contactinfo/ContactInfoCache.java index 4135cb7b8..6c35711a8 100644 --- a/java/com/android/dialer/app/contactinfo/ContactInfoCache.java +++ b/java/com/android/dialer/app/contactinfo/ContactInfoCache.java @@ -29,8 +29,8 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.PriorityBlockingQueue; /** - * This is a cache of contact details for the phone numbers in the c all log. The key is the phone - * number with the country in which teh call was placed or received. The content of the cache is + * This is a cache of contact details for the phone numbers in the call log. The key is the phone + * number with the country in which the call was placed or received. The content of the cache is * expired (but not purged) whenever the application comes to the foreground. * *

This cache queues request for information and queries for information on a background thread, diff --git a/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java b/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java index a8c718502..71e4a16ad 100644 --- a/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java +++ b/java/com/android/dialer/app/contactinfo/ContactPhotoLoader.java @@ -104,7 +104,7 @@ public class ContactPhotoLoader { final RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), bitmap); drawable.setAntiAlias(true); - drawable.setCornerRadius(bitmap.getHeight() / 2); + drawable.setCircular(true); return drawable; } catch (IOException e) { LogUtil.e("ContactPhotoLoader.createPhotoIconDrawable", e.toString()); diff --git a/java/com/android/dialer/app/dialpad/DialpadFragment.java b/java/com/android/dialer/app/dialpad/DialpadFragment.java index 18bb250ce..4785ab16f 100644 --- a/java/com/android/dialer/app/dialpad/DialpadFragment.java +++ b/java/com/android/dialer/app/dialpad/DialpadFragment.java @@ -78,9 +78,9 @@ import com.android.dialer.app.DialtactsActivity; import com.android.dialer.app.R; import com.android.dialer.app.SpecialCharSequenceMgr; import com.android.dialer.app.calllog.CallLogAsync; -import com.android.dialer.app.calllog.PhoneAccountUtils; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.callintent.nano.CallInitiationType; +import com.android.dialer.calllogutils.PhoneAccountUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.dialpadview.DialpadKeyButton; import com.android.dialer.dialpadview.DialpadView; @@ -598,6 +598,7 @@ public class DialpadFragment extends Fragment @Override public void onStart() { + LogUtil.d("DialpadFragment.onStart", "first launch: %b", mFirstLaunch); Trace.beginSection(TAG + " onStart"); super.onStart(); // if the mToneGenerator creation fails, just continue without it. It is @@ -624,6 +625,7 @@ public class DialpadFragment extends Fragment @Override public void onResume() { + LogUtil.d("DialpadFragment.onResume", ""); Trace.beginSection(TAG + " onResume"); super.onResume(); diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java index eef920710..9ec6042c0 100644 --- a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java +++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java @@ -134,11 +134,6 @@ public class BlockedNumbersSettingsActivity extends AppCompatActivity return 0; } - @Override - public int getActionBarHideOffset() { - return 0; - } - @Override public int getActionBarHeight() { return 0; diff --git a/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java b/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java index 2125a1524..1cdeb2175 100644 --- a/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java +++ b/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java @@ -17,12 +17,14 @@ package com.android.dialer.app.legacybindings; import android.app.Activity; +import android.support.annotation.NonNull; import android.view.ViewGroup; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.contactinfo.ContactInfoCache; import com.android.dialer.app.list.RegularSearchFragment; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; +import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; /** * These are old bindings between Dialer and the container application. All new bindings should be @@ -41,6 +43,7 @@ public interface DialerLegacyBindings { CallLogCache callLogCache, ContactInfoCache contactInfoCache, VoicemailPlaybackPresenter voicemailPlaybackPresenter, + @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType); RegularSearchFragment newRegularSearchFragment(); diff --git a/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java b/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java index f01df78f8..6e32843ba 100644 --- a/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java +++ b/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java @@ -17,12 +17,14 @@ package com.android.dialer.app.legacybindings; import android.app.Activity; +import android.support.annotation.NonNull; import android.view.ViewGroup; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.contactinfo.ContactInfoCache; import com.android.dialer.app.list.RegularSearchFragment; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; +import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; /** Default implementation for dialer legacy bindings. */ public class DialerLegacyBindingsStub implements DialerLegacyBindings { @@ -35,6 +37,7 @@ public class DialerLegacyBindingsStub implements DialerLegacyBindings { CallLogCache callLogCache, ContactInfoCache contactInfoCache, VoicemailPlaybackPresenter voicemailPlaybackPresenter, + @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType) { return new CallLogAdapter( activity, @@ -43,6 +46,7 @@ public class DialerLegacyBindingsStub implements DialerLegacyBindings { callLogCache, contactInfoCache, voicemailPlaybackPresenter, + filteredNumberAsyncQueryHandler, activityType); } diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java index 725ad3001..13938f29a 100644 --- a/java/com/android/dialer/app/list/ListsFragment.java +++ b/java/com/android/dialer/app/list/ListsFragment.java @@ -30,19 +30,16 @@ import android.support.annotation.Nullable; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.contacts.common.list.ViewPagerTabs; import com.android.dialer.app.R; import com.android.dialer.app.calllog.CallLogFragment; -import com.android.dialer.app.calllog.CallLogNotificationsHelper; +import com.android.dialer.app.calllog.CallLogNotificationsService; import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment; import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler; import com.android.dialer.app.voicemail.error.VoicemailStatusCorruptionHandler.Source; -import com.android.dialer.app.widget.ActionBarController; import com.android.dialer.common.LogUtil; import com.android.dialer.database.CallLogQueryHandler; import com.android.dialer.logging.Logger; @@ -92,7 +89,6 @@ public class ListsFragment extends Fragment public static final int TAB_COUNT_DEFAULT = 3; public static final int TAB_COUNT_WITH_VOICEMAIL = 4; private static final String TAG = "ListsFragment"; - private ActionBar mActionBar; private ViewPager mViewPager; private ViewPagerTabs mViewPagerTabs; private ViewPagerAdapter mViewPagerAdapter; @@ -108,8 +104,7 @@ public class ListsFragment extends Fragment private boolean mHasFetchedVoicemailStatus; private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched; private VoicemailStatusHelper mVoicemailStatusHelper; - private ArrayList mOnPageChangeListeners = - new ArrayList(); + private final ArrayList mOnPageChangeListeners = new ArrayList<>(); private String[] mTabTitles; private int[] mTabIcons; /** The position of the currently selected tab. */ @@ -149,7 +144,6 @@ public class ListsFragment extends Fragment Trace.beginSection(TAG + " onResume"); super.onResume(); - mActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (getUserVisibleHint()) { sendScreenViewForCurrentPosition(); } @@ -329,7 +323,7 @@ public class ListsFragment extends Fragment .putBoolean( VisualVoicemailEnabledChecker.PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, hasActiveVoicemailProvider) - .commit(); + .apply(); } if (hasActiveVoicemailProvider) { @@ -403,7 +397,7 @@ public class ListsFragment extends Fragment public void markMissedCallsAsReadAndRemoveNotifications() { if (mCallLogQueryHandler != null) { mCallLogQueryHandler.markMissedCallsAsRead(); - CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); + CallLogNotificationsService.markNewMissedCallsAsOld(getContext(), null); } } @@ -413,11 +407,6 @@ public class ListsFragment extends Fragment mRemoveView.animate().alpha(show ? 1 : 0).start(); } - public boolean shouldShowActionBar() { - // TODO: Update this based on scroll state. - return mActionBar != null; - } - public SpeedDialFragment getSpeedDialFragment() { return mSpeedDialFragment; } @@ -486,11 +475,6 @@ public class ListsFragment extends Fragment throw new IllegalStateException("No fragment at position " + position); } - public interface HostInterface { - - ActionBarController getActionBarController(); - } - public class ViewPagerAdapter extends FragmentPagerAdapter { private final List mFragments = new ArrayList<>(); @@ -518,7 +502,7 @@ public class ListsFragment extends Fragment return mSpeedDialFragment; case TAB_INDEX_HISTORY: if (mHistoryFragment == null) { - mHistoryFragment = new CallLogFragment(); + mHistoryFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL); } return mHistoryFragment; case TAB_INDEX_ALL_CONTACTS: diff --git a/java/com/android/dialer/app/list/SearchFragment.java b/java/com/android/dialer/app/list/SearchFragment.java index 4a7d48ae4..e6615aa8d 100644 --- a/java/com/android/dialer/app/list/SearchFragment.java +++ b/java/com/android/dialer/app/list/SearchFragment.java @@ -98,6 +98,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { @Override public void onStart() { + LogUtil.d("SearchFragment.onStart", ""); super.onStart(); if (isSearchMode()) { getAdapter().setHasHeader(0, false); @@ -301,6 +302,7 @@ public class SearchFragment extends PhoneNumberPickerFragment { * shown. This can be optionally animated. */ public void updatePosition(boolean animate) { + LogUtil.d("SearchFragment.updatePosition", "animate: %b", animate); if (mActivity == null) { // Activity will be set in onStart, and this method will be called again return; @@ -363,6 +365,13 @@ public class SearchFragment extends PhoneNumberPickerFragment { return; } int spacerHeight = mActivity.isDialpadShown() ? mActivity.getDialpadHeight() : 0; + LogUtil.d( + "SearchFragment.resizeListView", + "spacerHeight: %d -> %d, isDialpadShown: %b, dialpad height: %d", + mSpacer.getHeight(), + spacerHeight, + mActivity.isDialpadShown(), + mActivity.getDialpadHeight()); if (spacerHeight != mSpacer.getHeight()) { final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpacer.getLayoutParams(); lp.height = spacerHeight; @@ -418,8 +427,6 @@ public class SearchFragment extends PhoneNumberPickerFragment { int getDialpadHeight(); - int getActionBarHideOffset(); - int getActionBarHeight(); } } diff --git a/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml b/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml index 247b34f4c..7e450c4cd 100644 --- a/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml +++ b/java/com/android/dialer/app/manifests/activities/AndroidManifest.xml @@ -29,18 +29,6 @@ android:theme="@style/SettingsStyle"> - - - - - - - - diff --git a/java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png deleted file mode 100644 index 14a33e39f..000000000 Binary files a/java/com/android/dialer/app/res/drawable-hdpi/ic_call_arrow.png and /dev/null differ diff --git a/java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png deleted file mode 100644 index 169cf2934..000000000 Binary files a/java/com/android/dialer/app/res/drawable-mdpi/ic_call_arrow.png and /dev/null differ diff --git a/java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png deleted file mode 100644 index 6f1366018..000000000 Binary files a/java/com/android/dialer/app/res/drawable-xhdpi/ic_call_arrow.png and /dev/null differ diff --git a/java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png deleted file mode 100644 index 0364ee015..000000000 Binary files a/java/com/android/dialer/app/res/drawable-xxhdpi/ic_call_arrow.png and /dev/null differ diff --git a/java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png b/java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png deleted file mode 100644 index 8243c2536..000000000 Binary files a/java/com/android/dialer/app/res/drawable-xxxhdpi/ic_call_arrow.png and /dev/null differ diff --git a/java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png b/java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png new file mode 100644 index 000000000..ff55620d0 Binary files /dev/null and b/java/com/android/dialer/app/res/drawable-xxxhdpi/search_shadow.9.png differ diff --git a/java/com/android/dialer/app/res/layout/call_detail.xml b/java/com/android/dialer/app/res/layout/call_detail.xml deleted file mode 100644 index 58a7bf0dc..000000000 --- a/java/com/android/dialer/app/res/layout/call_detail.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git a/java/com/android/dialer/app/res/layout/call_detail_footer.xml b/java/com/android/dialer/app/res/layout/call_detail_footer.xml deleted file mode 100644 index 57713448e..000000000 --- a/java/com/android/dialer/app/res/layout/call_detail_footer.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - diff --git a/java/com/android/dialer/app/res/layout/call_detail_header.xml b/java/com/android/dialer/app/res/layout/call_detail_header.xml deleted file mode 100644 index fd85f0af1..000000000 --- a/java/com/android/dialer/app/res/layout/call_detail_header.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/java/com/android/dialer/app/res/layout/call_detail_history_item.xml b/java/com/android/dialer/app/res/layout/call_detail_history_item.xml index 5958ee81c..0184a42f2 100644 --- a/java/com/android/dialer/app/res/layout/call_detail_history_item.xml +++ b/java/com/android/dialer/app/res/layout/call_detail_history_item.xml @@ -27,9 +27,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> - diff --git a/java/com/android/dialer/app/res/layout/call_log_activity.xml b/java/com/android/dialer/app/res/layout/call_log_activity.xml new file mode 100644 index 000000000..4e2b1887c --- /dev/null +++ b/java/com/android/dialer/app/res/layout/call_log_activity.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/java/com/android/dialer/app/res/layout/call_log_list_item.xml b/java/com/android/dialer/app/res/layout/call_log_list_item.xml index c22ac861d..1592aa928 100644 --- a/java/com/android/dialer/app/res/layout/call_log_list_item.xml +++ b/java/com/android/dialer/app/res/layout/call_log_list_item.xml @@ -93,8 +93,7 @@ android:layout_height="wrap_content" android:orientation="horizontal"> - + +

+ + diff --git a/java/com/android/dialer/app/res/menu/dialtacts_options.xml b/java/com/android/dialer/app/res/menu/dialtacts_options.xml index 434aa81d9..25a3e1811 100644 --- a/java/com/android/dialer/app/res/menu/dialtacts_options.xml +++ b/java/com/android/dialer/app/res/menu/dialtacts_options.xml @@ -16,13 +16,17 @@ + android:id="@+id/menu_history" + android:icon="@drawable/ic_menu_history_lt" + android:title="@string/action_menu_call_history_description"/> + diff --git a/java/com/android/dialer/app/res/values/colors.xml b/java/com/android/dialer/app/res/values/colors.xml index b88e55276..cf6b926be 100644 --- a/java/com/android/dialer/app/res/values/colors.xml +++ b/java/com/android/dialer/app/res/values/colors.xml @@ -16,7 +16,6 @@ #ff1744 - #00c853 #fff @color/dialer_theme_color @@ -84,13 +83,6 @@ as call back, play voicemail, etc. --> @color/dialer_theme_color - - #ff2e58 - - @color/dialer_green_highlight_color - - @color/dialer_secondary_text_color - @color/dialer_theme_color_20pct #80ffffff diff --git a/java/com/android/dialer/app/res/values/dimens.xml b/java/com/android/dialer/app/res/values/dimens.xml index f3fd63350..7da29c7a3 100644 --- a/java/com/android/dialer/app/res/values/dimens.xml +++ b/java/com/android/dialer/app/res/values/dimens.xml @@ -28,7 +28,6 @@ 8dp 32dp 54dp - 4dp 13dp 8dp 8dp @@ -68,7 +67,7 @@ 76% 12dp 9dp - 2dp + 1dp 0dp 1dp @@ -143,6 +142,4 @@ 14sp 14sp 48dp - - 12dp diff --git a/java/com/android/dialer/app/res/values/strings.xml b/java/com/android/dialer/app/res/values/strings.xml index 689ee1ba8..66bf70f1a 100644 --- a/java/com/android/dialer/app/res/values/strings.xml +++ b/java/com/android/dialer/app/res/values/strings.xml @@ -55,9 +55,6 @@ Unblock number - - Delete - Edit number before call @@ -94,7 +91,7 @@ Missed calls - %s missed calls + %d missed calls @@ -251,15 +248,13 @@ Settings + + Simulator + All contacts - - Call details - - - Details not available - @@ -275,52 +270,6 @@ is already in progress.) --> Add call - - Incoming call - - - Incoming call transferred to another device - - - Outgoing call - - - Outgoing call transferred to another device - - - Missed call - - - Incoming video call - - - Incoming video call transferred to another device - - - Outgoing video call - - - Outgoing video call transferred to another device - - - Missed video call - - - Voicemail - - - Declined call - - - Blocked call - - - Call answered on another device - Incoming calls @@ -623,28 +572,10 @@ [CHAR LIMIT=NONE] --> Call to ^1, ^2, ^3, ^4. - - on ^1 - - - via %1$s - via %1$s - - on %1$s, via %2$s - @@ -827,6 +758,9 @@ Call blocking + + Voicemail + @@ -955,6 +889,6 @@ Spam - %1$s unavailable right now + %1$s is offline and can\'t be reached diff --git a/java/com/android/dialer/app/res/values/styles.xml b/java/com/android/dialer/app/res/values/styles.xml index ac4422ba2..24521ddaf 100644 --- a/java/com/android/dialer/app/res/values/styles.xml +++ b/java/com/android/dialer/app/res/values/styles.xml @@ -111,11 +111,6 @@ @null - - - - - - \ No newline at end of file diff --git a/java/com/android/dialer/callcomposer/res/values/values.xml b/java/com/android/dialer/callcomposer/res/values/values.xml new file mode 100644 index 000000000..39b8e4071 --- /dev/null +++ b/java/com/android/dialer/callcomposer/res/values/values.xml @@ -0,0 +1,20 @@ + + + + 2 + false + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/AndroidManifest.xml b/java/com/android/dialer/calldetails/AndroidManifest.xml new file mode 100644 index 000000000..b71207ba2 --- /dev/null +++ b/java/com/android/dialer/calldetails/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java new file mode 100644 index 000000000..6070640a0 --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java @@ -0,0 +1,130 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.widget.Toolbar; +import com.android.dialer.callcomposer.nano.CallComposerContact; +import com.android.dialer.calldetails.nano.CallDetailsEntries; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.common.Assert; +import com.android.dialer.common.AsyncTaskExecutors; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.DialerImpression; +import com.android.dialer.protos.ProtoParsers; + +/** Displays the details of a specific call log entry. */ +public class CallDetailsActivity extends AppCompatActivity { + + private static final String EXTRA_CALL_DETAILS_ENTRIES = "call_details_entries"; + private static final String EXTRA_CONTACT = "contact"; + private static final String TASK_DELETE = "task_delete"; + + private CallDetailsEntry[] entries; + + public static Intent newInstance( + Context context, @NonNull CallDetailsEntries details, @NonNull CallComposerContact contact) { + Assert.isNotNull(details); + Assert.isNotNull(contact); + + Intent intent = new Intent(context, CallDetailsActivity.class); + ProtoParsers.put(intent, EXTRA_CONTACT, contact); + ProtoParsers.put(intent, EXTRA_CALL_DETAILS_ENTRIES, details); + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.call_details_activity); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setActionBar(toolbar); + toolbar.inflateMenu(R.menu.call_details_menu); + toolbar.setNavigationOnClickListener(v -> finish()); + onHandleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + onHandleIntent(intent); + } + + private void onHandleIntent(Intent intent) { + Bundle arguments = intent.getExtras(); + CallComposerContact contact = + ProtoParsers.getFromInstanceState(arguments, EXTRA_CONTACT, new CallComposerContact()); + entries = + ProtoParsers.getFromInstanceState( + arguments, EXTRA_CALL_DETAILS_ENTRIES, new CallDetailsEntries()) + .entries; + RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(new CallDetailsAdapter(this, contact, entries)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.call_detail_delete_menu_item) { + Logger.get(this).logImpression(DialerImpression.Type.USER_DELETED_CALL_LOG_ITEM); + AsyncTaskExecutors.createAsyncTaskExecutor().submit(TASK_DELETE, new DeleteCallsTask()); + item.setEnabled(false); + return true; + } + return super.onOptionsItemSelected(item); + } + + /** Delete specified calls from the call log. */ + private class DeleteCallsTask extends AsyncTask { + + private final String callIds; + + DeleteCallsTask() { + StringBuilder callIds = new StringBuilder(); + for (CallDetailsEntry entry : entries) { + if (callIds.length() != 0) { + callIds.append(","); + } + callIds.append(entry.callId); + } + this.callIds = callIds.toString(); + } + + @Override + protected Void doInBackground(Void... params) { + getContentResolver() + .delete(Calls.CONTENT_URI, CallLog.Calls._ID + " IN (" + callIds + ")", null); + return null; + } + + @Override + public void onPostExecute(Void result) { + finish(); + } + } +} diff --git a/java/com/android/dialer/calldetails/CallDetailsAdapter.java b/java/com/android/dialer/calldetails/CallDetailsAdapter.java new file mode 100644 index 000000000..954583077 --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsAdapter.java @@ -0,0 +1,97 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import com.android.dialer.callcomposer.nano.CallComposerContact; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calllogutils.CallTypeHelper; +import com.android.dialer.common.Assert; + +/** Adapter for RecyclerView in {@link CallDetailsActivity}. */ +public class CallDetailsAdapter extends RecyclerView.Adapter { + + private static final int HEADER_VIEW_TYPE = 1; + private static final int CALL_ENTRY_VIEW_TYPE = 2; + private static final int FOOTER_VIEW_TYPE = 3; + + private final CallComposerContact contact; + private final CallDetailsEntry[] callDetailsEntries; + private final CallTypeHelper callTypeHelper; + + public CallDetailsAdapter( + Context context, CallComposerContact contact, CallDetailsEntry[] callDetailsEntries) { + this.contact = Assert.isNotNull(contact); + this.callDetailsEntries = Assert.isNotNull(callDetailsEntries); + callTypeHelper = new CallTypeHelper(context.getResources()); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case HEADER_VIEW_TYPE: + return new CallDetailsHeaderViewHolder( + inflater.inflate(R.layout.contact_container, parent, false)); + case CALL_ENTRY_VIEW_TYPE: + return new CallDetailsEntryViewHolder( + inflater.inflate(R.layout.call_details_entry, parent, false)); + case FOOTER_VIEW_TYPE: + return new CallDetailsFooterViewHolder( + inflater.inflate(R.layout.call_details_footer, parent, false)); + default: + Assert.fail("No ViewHolder available for viewType: " + viewType); + return null; + } + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + if (position == 0) { // Header + ((CallDetailsHeaderViewHolder) holder).updateContactInfo(contact); + } else if (position == getItemCount() - 1) { + ((CallDetailsFooterViewHolder) holder).setPhoneNumber(contact.number); + } else { + CallDetailsEntryViewHolder viewHolder = (CallDetailsEntryViewHolder) holder; + viewHolder.setCallDetails( + contact.number, + callDetailsEntries[position - 1], + callTypeHelper, + position != getItemCount() - 2); + } + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { // Header + return HEADER_VIEW_TYPE; + } else if (position == getItemCount() - 1) { + return FOOTER_VIEW_TYPE; + } else { + return CALL_ENTRY_VIEW_TYPE; + } + } + + @Override + public int getItemCount() { + return callDetailsEntries.length + 2; // Header + footer + } +} diff --git a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java new file mode 100644 index 000000000..b1a70af0c --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java @@ -0,0 +1,195 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.CallLog.Calls; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView.ViewHolder; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.calllogutils.CallEntryFormatter; +import com.android.dialer.calllogutils.CallTypeHelper; +import com.android.dialer.common.LogUtil; +import com.android.dialer.compat.AppCompatConstants; +import com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult; +import com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type; +import com.android.dialer.util.CallUtil; +import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.IntentUtil; + +/** ViewHolder for call entries in {@link CallDetailsActivity}. */ +public class CallDetailsEntryViewHolder extends ViewHolder { + + private final ImageView callTypeIcon; + private final TextView callTypeText; + private final TextView callTime; + private final TextView callDuration; + + private final View multimediaImageContainer; + private final View multimediaDetailsContainer; + private final View multimediaDivider; + + private final TextView multimediaDetails; + + private final ImageView multimediaImage; + + // TODO: Display this when location is stored - b/36160042 + @SuppressWarnings("unused") + private final TextView multimediaAttachmentsNumber; + + private final Context context; + + public CallDetailsEntryViewHolder(View container) { + super(container); + context = container.getContext(); + + callTypeIcon = (ImageView) container.findViewById(R.id.call_direction); + callTypeText = (TextView) container.findViewById(R.id.call_type); + callTime = (TextView) container.findViewById(R.id.call_time); + callDuration = (TextView) container.findViewById(R.id.call_duration); + + multimediaImageContainer = container.findViewById(R.id.multimedia_image_container); + multimediaDetailsContainer = container.findViewById(R.id.ec_container); + multimediaDivider = container.findViewById(R.id.divider); + multimediaDetails = (TextView) container.findViewById(R.id.multimedia_details); + multimediaImage = (ImageView) container.findViewById(R.id.multimedia_image); + multimediaAttachmentsNumber = + (TextView) container.findViewById(R.id.multimedia_attachments_number); + } + + void setCallDetails( + String number, + CallDetailsEntry entry, + CallTypeHelper callTypeHelper, + boolean showMultimediaDivider) { + int callType = entry.callType; + boolean isVideoCall = + (entry.features & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO + && CallUtil.isVideoEnabled(context); + boolean isPulledCall = + (entry.features & Calls.FEATURES_PULLED_EXTERNALLY) == Calls.FEATURES_PULLED_EXTERNALLY; + + Drawable callIcon = getIconForCallType(context.getResources(), callType); + int color = getColorForCallType(context, callType); + callIcon.setColorFilter(color, PorterDuff.Mode.MULTIPLY); + callTime.setTextColor(color); + callTypeIcon.setImageDrawable(callIcon); + + callTypeText.setText(callTypeHelper.getCallTypeText(callType, isVideoCall, isPulledCall)); + callTime.setText(CallEntryFormatter.formatDate(context, entry.date)); + if (CallTypeHelper.isMissedCallType(callType)) { + callDuration.setVisibility(View.GONE); + } else { + callDuration.setVisibility(View.VISIBLE); + callDuration.setText( + CallEntryFormatter.formatDurationAndDataUsage(context, entry.duration, entry.dataUsage)); + } + setMultimediaDetails(number, entry, showMultimediaDivider); + } + + private void setMultimediaDetails(String number, CallDetailsEntry entry, boolean showDivider) { + multimediaDivider.setVisibility(showDivider ? View.VISIBLE : View.GONE); + if (entry.historyResults == null || entry.historyResults.length <= 0) { + LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no data, hiding UI"); + multimediaDetailsContainer.setVisibility(View.GONE); + } else { + + // TODO: b/36158891 Add room for 2 pieces of enriched call data. It's possible + // to have both call composer data and post call data for a single call. + HistoryResult historyResult = entry.historyResults[0]; + multimediaDetailsContainer.setVisibility(View.VISIBLE); + multimediaDetailsContainer.setOnClickListener( + (v) -> { + DialerUtils.startActivityWithErrorToast(context, IntentUtil.getSendSmsIntent(number)); + }); + multimediaImageContainer.setClipToOutline(true); + + if (!TextUtils.isEmpty(historyResult.imageUri)) { + LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "setting image"); + multimediaImageContainer.setVisibility(View.VISIBLE); + multimediaImage.setImageURI(Uri.parse(historyResult.imageUri)); + multimediaDetails.setText( + isIncoming(historyResult) ? R.string.received_a_photo : R.string.sent_a_photo); + } else { + LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no image"); + } + + // Set text after image to overwrite the received/sent a photo text + if (!TextUtils.isEmpty(historyResult.text)) { + LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "showing text"); + multimediaDetails.setText( + context.getString(R.string.message_in_quotes, historyResult.text)); + } else { + LogUtil.i("CallDetailsEntryViewHolder.setMultimediaDetails", "no text"); + } + } + } + + private static boolean isIncoming(@NonNull HistoryResult historyResult) { + return historyResult.type == Type.INCOMING_POST_CALL + || historyResult.type == Type.INCOMING_CALL_COMPOSER; + } + + private static Drawable getIconForCallType(Resources resources, int callType) { + switch (callType) { + case AppCompatConstants.CALLS_OUTGOING_TYPE: + return resources.getDrawable(R.drawable.quantum_ic_call_made_white_24); + case AppCompatConstants.CALLS_BLOCKED_TYPE: + return resources.getDrawable(R.drawable.quantum_ic_block_white_24); + case AppCompatConstants.CALLS_INCOMING_TYPE: + case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: + case AppCompatConstants.CALLS_REJECTED_TYPE: + return resources.getDrawable(R.drawable.quantum_ic_call_received_white_24); + case AppCompatConstants.CALLS_MISSED_TYPE: + default: + // It is possible for users to end up with calls with unknown call types in their + // call history, possibly due to 3rd party call log implementations (e.g. to + // distinguish between rejected and missed calls). Instead of crashing, just + // assume that all unknown call types are missed calls. + return resources.getDrawable(R.drawable.quantum_ic_call_missed_white_24); + } + } + + private static @ColorInt int getColorForCallType(Context context, int callType) { + switch (callType) { + case AppCompatConstants.CALLS_OUTGOING_TYPE: + case AppCompatConstants.CALLS_VOICEMAIL_TYPE: + case AppCompatConstants.CALLS_BLOCKED_TYPE: + case AppCompatConstants.CALLS_INCOMING_TYPE: + case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: + case AppCompatConstants.CALLS_REJECTED_TYPE: + return ContextCompat.getColor(context, R.color.dialer_secondary_text_color); + case AppCompatConstants.CALLS_MISSED_TYPE: + default: + // It is possible for users to end up with calls with unknown call types in their + // call history, possibly due to 3rd party call log implementations (e.g. to + // distinguish between rejected and missed calls). Instead of crashing, just + // assume that all unknown call types are missed calls. + return ContextCompat.getColor(context, R.color.missed_call); + } + } +} diff --git a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java new file mode 100644 index 000000000..36662bab9 --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java @@ -0,0 +1,67 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.View.OnClickListener; +import com.android.contacts.common.ClipboardUtils; +import com.android.dialer.common.Assert; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.DialerImpression; +import com.android.dialer.util.CallUtil; +import com.android.dialer.util.DialerUtils; + +/** ViewHolder container for {@link CallDetailsActivity} footer. */ +public class CallDetailsFooterViewHolder extends RecyclerView.ViewHolder + implements OnClickListener { + + private final View copy; + private final View edit; + + private String number; + + public CallDetailsFooterViewHolder(View view) { + super(view); + copy = view.findViewById(R.id.call_detail_action_copy); + edit = view.findViewById(R.id.call_detail_action_edit_before_call); + + copy.setOnClickListener(this); + edit.setOnClickListener(this); + } + + public void setPhoneNumber(String number) { + this.number = number; + } + + @Override + public void onClick(View view) { + Context context = view.getContext(); + if (view == copy) { + Logger.get(context).logImpression(DialerImpression.Type.CALL_DETAILS_COPY_NUMBER); + ClipboardUtils.copyText(context, null, number, true); + } else if (view == edit) { + Logger.get(context).logImpression(DialerImpression.Type.CALL_DETAILS_EDIT_BEFORE_CALL); + Intent dialIntent = new Intent(Intent.ACTION_DIAL, CallUtil.getCallUri(number)); + DialerUtils.startActivityWithErrorToast(context, dialIntent); + } else { + Assert.fail("View on click not implemented: " + view); + } + } +} diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java new file mode 100644 index 000000000..1679c2baf --- /dev/null +++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java @@ -0,0 +1,125 @@ +/* + * 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.calldetails; + +import android.content.Context; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.QuickContactBadge; +import android.widget.TextView; +import com.android.contacts.common.ContactPhotoManager; +import com.android.contacts.common.util.UriUtils; +import com.android.dialer.callcomposer.nano.CallComposerContact; +import com.android.dialer.callintent.CallIntentBuilder; +import com.android.dialer.callintent.nano.CallInitiationType; +import com.android.dialer.common.Assert; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.DialerImpression; +import com.android.dialer.util.DialerUtils; + +/** ViewHolder for Header/Contact in {@link CallDetailsActivity}. */ +public class CallDetailsHeaderViewHolder extends RecyclerView.ViewHolder + implements OnClickListener { + + private final View callBackButton; + private final TextView nameView; + private final TextView numberView; + private final QuickContactBadge contactPhoto; + private final Context context; + + private CallComposerContact contact; + + CallDetailsHeaderViewHolder(View container) { + super(container); + context = container.getContext(); + callBackButton = container.findViewById(R.id.call_back_button); + nameView = (TextView) container.findViewById(R.id.contact_name); + numberView = (TextView) container.findViewById(R.id.phone_number); + contactPhoto = (QuickContactBadge) container.findViewById(R.id.quick_contact_photo); + callBackButton.setOnClickListener(this); + } + + /** + * Populates the contact info fields based on the current contact information. Copied from {@link + * com.android.contacts.common.dialog.CallSubjectDialog}. + */ + public void updateContactInfo(CallComposerContact contact) { + this.contact = contact; + setPhoto( + contact.photoId, + Uri.parse(contact.photoUri), + Uri.parse(contact.contactUri), + contact.nameOrNumber, + contact.isBusiness); + + nameView.setText(contact.nameOrNumber); + if (!TextUtils.isEmpty(contact.numberLabel) && !TextUtils.isEmpty(contact.displayNumber)) { + numberView.setVisibility(View.VISIBLE); + String secondaryInfo = + context.getString( + com.android.contacts.common.R.string.call_subject_type_and_number, + contact.numberLabel, + contact.displayNumber); + numberView.setText(secondaryInfo); + } else { + numberView.setVisibility(View.GONE); + numberView.setText(null); + } + } + + /** + * Sets the photo on the quick contact galleryIcon. Copied from {@link + * com.android.contacts.common.dialog.CallSubjectDialog}. + */ + private void setPhoto( + long photoId, Uri photoUri, Uri contactUri, String displayName, boolean isBusiness) { + contactPhoto.assignContactUri(contactUri); + contactPhoto.setOverlay(null); + + int contactType = + isBusiness ? ContactPhotoManager.TYPE_BUSINESS : ContactPhotoManager.TYPE_DEFAULT; + String lookupKey = contactUri == null ? null : UriUtils.getLookupKeyFromUri(contactUri); + + ContactPhotoManager.DefaultImageRequest request = + new ContactPhotoManager.DefaultImageRequest( + displayName, lookupKey, contactType, true /* isCircular */); + + if (photoId == 0 && photoUri != null) { + contactPhoto.setImageDrawable( + context.getDrawable(R.drawable.product_logo_avatar_anonymous_color_120)); + } else { + ContactPhotoManager.getInstance(context) + .loadThumbnail( + contactPhoto, photoId, false /* darkTheme */, true /* isCircular */, request); + } + } + + @Override + public void onClick(View view) { + if (view == callBackButton) { + Logger.get(view.getContext()).logImpression(DialerImpression.Type.CALL_DETAILS_CALL_BACK); + DialerUtils.startActivityWithErrorToast( + view.getContext(), + new CallIntentBuilder(contact.number, CallInitiationType.Type.CALL_DETAILS).build()); + } else { + Assert.fail("View OnClickListener not implemented: " + view); + } + } +} diff --git a/java/com/android/dialer/calldetails/nano/CallDetailsEntries.java b/java/com/android/dialer/calldetails/nano/CallDetailsEntries.java new file mode 100644 index 000000000..aee8f3652 --- /dev/null +++ b/java/com/android/dialer/calldetails/nano/CallDetailsEntries.java @@ -0,0 +1,440 @@ +/* + * 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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! + +package com.android.dialer.calldetails.nano; + +/** This file is autogenerated, but javadoc required. */ +@SuppressWarnings("hiding") +public final class CallDetailsEntries + extends com.google.protobuf.nano.ExtendableMessageNano { + + /** This file is autogenerated, but javadoc required. */ + public static final class CallDetailsEntry + extends com.google.protobuf.nano.ExtendableMessageNano { + + private static volatile CallDetailsEntry[] _emptyArray; + public static CallDetailsEntry[] emptyArray() { + // Lazily initializes the empty array + if (_emptyArray == null) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + if (_emptyArray == null) { + _emptyArray = new CallDetailsEntry[0]; + } + } + } + return _emptyArray; + } + + // optional int64 call_id = 1; + public long callId; + + // optional int32 call_type = 2; + public int callType; + + // optional int32 features = 3; + public int features; + + // optional int64 date = 4; + public long date; + + // optional int64 duration = 5; + public long duration; + + // optional int64 data_usage = 6; + public long dataUsage; + + // repeated .com.android.dialer.enrichedcall.historyquery.proto. + // HistoryResult history_results = 7; + public com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult[] historyResults; + + // @@protoc_insertion_point(class_scope:com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry) + + public CallDetailsEntry() { + clear(); + } + + public CallDetailsEntry clear() { + callId = 0L; + callType = 0; + features = 0; + date = 0L; + duration = 0L; + dataUsage = 0L; + historyResults = + com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.emptyArray(); + unknownFieldData = null; + cachedSize = -1; + return this; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof CallDetailsEntry)) { + return false; + } + CallDetailsEntry other = (CallDetailsEntry) o; + if (this.callId != other.callId) { + return false; + } + if (this.callType != other.callType) { + return false; + } + if (this.features != other.features) { + return false; + } + if (this.date != other.date) { + return false; + } + if (this.duration != other.duration) { + return false; + } + if (this.dataUsage != other.dataUsage) { + return false; + } + if (!com.google.protobuf.nano.InternalNano.equals( + this.historyResults, other.historyResults)) { + return false; + } + if (unknownFieldData == null || unknownFieldData.isEmpty()) { + return other.unknownFieldData == null || other.unknownFieldData.isEmpty(); + } else { + return unknownFieldData.equals(other.unknownFieldData); + } + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + getClass().getName().hashCode(); + result = 31 * result + (int) (this.callId ^ (this.callId >>> 32)); + result = 31 * result + this.callType; + result = 31 * result + this.features; + result = 31 * result + (int) (this.date ^ (this.date >>> 32)); + result = 31 * result + (int) (this.duration ^ (this.duration >>> 32)); + result = 31 * result + (int) (this.dataUsage ^ (this.dataUsage >>> 32)); + result = 31 * result + com.google.protobuf.nano.InternalNano.hashCode(this.historyResults); + result = + 31 * result + + (unknownFieldData == null || unknownFieldData.isEmpty() + ? 0 + : unknownFieldData.hashCode()); + return result; + } + + @Override + public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output) + throws java.io.IOException { + if (this.callId != 0L) { + output.writeInt64(1, this.callId); + } + if (this.callType != 0) { + output.writeInt32(2, this.callType); + } + if (this.features != 0) { + output.writeInt32(3, this.features); + } + if (this.date != 0L) { + output.writeInt64(4, this.date); + } + if (this.duration != 0L) { + output.writeInt64(5, this.duration); + } + if (this.dataUsage != 0L) { + output.writeInt64(6, this.dataUsage); + } + if (this.historyResults != null && this.historyResults.length > 0) { + for (int i = 0; i < this.historyResults.length; i++) { + com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult element = + this.historyResults[i]; + if (element != null) { + output.writeMessage(7, element); + } + } + } + super.writeTo(output); + } + + @Override + protected int computeSerializedSize() { + int size = super.computeSerializedSize(); + if (this.callId != 0L) { + size += com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt64Size(1, this.callId); + } + if (this.callType != 0) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt32Size(2, this.callType); + } + if (this.features != 0) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt32Size(3, this.features); + } + if (this.date != 0L) { + size += com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt64Size(4, this.date); + } + if (this.duration != 0L) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt64Size(5, this.duration); + } + if (this.dataUsage != 0L) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt64Size(6, this.dataUsage); + } + if (this.historyResults != null && this.historyResults.length > 0) { + for (int i = 0; i < this.historyResults.length; i++) { + com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult element = + this.historyResults[i]; + if (element != null) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeMessageSize(7, element); + } + } + } + return size; + } + + @Override + public CallDetailsEntry mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) + throws java.io.IOException { + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; + } + case 8: + { + this.callId = input.readInt64(); + break; + } + case 16: + { + this.callType = input.readInt32(); + break; + } + case 24: + { + this.features = input.readInt32(); + break; + } + case 32: + { + this.date = input.readInt64(); + break; + } + case 40: + { + this.duration = input.readInt64(); + break; + } + case 48: + { + this.dataUsage = input.readInt64(); + break; + } + case 58: + { + int arrayLength = + com.google.protobuf.nano.WireFormatNano.getRepeatedFieldArrayLength(input, 58); + int i = this.historyResults == null ? 0 : this.historyResults.length; + com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult[] newArray = + new com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult + [i + arrayLength]; + if (i != 0) { + java.lang.System.arraycopy(this.historyResults, 0, newArray, 0, i); + } + for (; i < newArray.length - 1; i++) { + newArray[i] = + new com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult(); + input.readMessage(newArray[i]); + input.readTag(); + } + // Last one without readTag. + newArray[i] = + new com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult(); + input.readMessage(newArray[i]); + this.historyResults = newArray; + break; + } + } + } + } + + public static CallDetailsEntry parseFrom(byte[] data) + throws com.google.protobuf.nano.InvalidProtocolBufferNanoException { + return com.google.protobuf.nano.MessageNano.mergeFrom(new CallDetailsEntry(), data); + } + + public static CallDetailsEntry parseFrom( + com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { + return new CallDetailsEntry().mergeFrom(input); + } + } + + private static volatile CallDetailsEntries[] _emptyArray; + public static CallDetailsEntries[] emptyArray() { + // Lazily initializes the empty array + if (_emptyArray == null) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + if (_emptyArray == null) { + _emptyArray = new CallDetailsEntries[0]; + } + } + } + return _emptyArray; + } + + // repeated .com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry entries = 1; + public com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry[] entries; + + // @@protoc_insertion_point(class_scope:com.android.dialer.calldetails.CallDetailsEntries) + + public CallDetailsEntries() { + clear(); + } + + public CallDetailsEntries clear() { + entries = com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry.emptyArray(); + unknownFieldData = null; + cachedSize = -1; + return this; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof CallDetailsEntries)) { + return false; + } + CallDetailsEntries other = (CallDetailsEntries) o; + if (!com.google.protobuf.nano.InternalNano.equals(this.entries, other.entries)) { + return false; + } + if (unknownFieldData == null || unknownFieldData.isEmpty()) { + return other.unknownFieldData == null || other.unknownFieldData.isEmpty(); + } else { + return unknownFieldData.equals(other.unknownFieldData); + } + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + getClass().getName().hashCode(); + result = 31 * result + com.google.protobuf.nano.InternalNano.hashCode(this.entries); + result = + 31 * result + + (unknownFieldData == null || unknownFieldData.isEmpty() + ? 0 + : unknownFieldData.hashCode()); + return result; + } + + @Override + public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output) + throws java.io.IOException { + if (this.entries != null && this.entries.length > 0) { + for (int i = 0; i < this.entries.length; i++) { + com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry element = + this.entries[i]; + if (element != null) { + output.writeMessage(1, element); + } + } + } + super.writeTo(output); + } + + @Override + protected int computeSerializedSize() { + int size = super.computeSerializedSize(); + if (this.entries != null && this.entries.length > 0) { + for (int i = 0; i < this.entries.length; i++) { + com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry element = + this.entries[i]; + if (element != null) { + size += com.google.protobuf.nano.CodedOutputByteBufferNano.computeMessageSize(1, element); + } + } + } + return size; + } + + @Override + public CallDetailsEntries mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) + throws java.io.IOException { + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; + } + case 10: + { + int arrayLength = + com.google.protobuf.nano.WireFormatNano.getRepeatedFieldArrayLength(input, 10); + int i = this.entries == null ? 0 : this.entries.length; + com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry[] newArray = + new com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry + [i + arrayLength]; + if (i != 0) { + java.lang.System.arraycopy(this.entries, 0, newArray, 0, i); + } + for (; i < newArray.length - 1; i++) { + newArray[i] = + new com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry(); + input.readMessage(newArray[i]); + input.readTag(); + } + // Last one without readTag. + newArray[i] = + new com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry(); + input.readMessage(newArray[i]); + this.entries = newArray; + break; + } + } + } + } + + public static CallDetailsEntries parseFrom(byte[] data) + throws com.google.protobuf.nano.InvalidProtocolBufferNanoException { + return com.google.protobuf.nano.MessageNano.mergeFrom(new CallDetailsEntries(), data); + } + + public static CallDetailsEntries parseFrom( + com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { + return new CallDetailsEntries().mergeFrom(input); + } +} diff --git a/java/com/android/dialer/calldetails/res/drawable/multimedia_image_background.xml b/java/com/android/dialer/calldetails/res/drawable/multimedia_image_background.xml new file mode 100644 index 000000000..421bdbfee --- /dev/null +++ b/java/com/android/dialer/calldetails/res/drawable/multimedia_image_background.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/java/com/android/dialer/calldetails/res/layout/call_details_activity.xml b/java/com/android/dialer/calldetails/res/layout/call_details_activity.xml new file mode 100644 index 000000000..038a8745e --- /dev/null +++ b/java/com/android/dialer/calldetails/res/layout/call_details_activity.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/res/layout/call_details_entry.xml b/java/com/android/dialer/calldetails/res/layout/call_details_entry.xml new file mode 100644 index 000000000..7f8bb8087 --- /dev/null +++ b/java/com/android/dialer/calldetails/res/layout/call_details_entry.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/res/layout/call_details_footer.xml b/java/com/android/dialer/calldetails/res/layout/call_details_footer.xml new file mode 100644 index 000000000..885cb0989 --- /dev/null +++ b/java/com/android/dialer/calldetails/res/layout/call_details_footer.xml @@ -0,0 +1,43 @@ + + + + + + + + + + diff --git a/java/com/android/dialer/calldetails/res/layout/contact_container.xml b/java/com/android/dialer/calldetails/res/layout/contact_container.xml new file mode 100644 index 000000000..95fe189b2 --- /dev/null +++ b/java/com/android/dialer/calldetails/res/layout/contact_container.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/res/layout/ec_data_container.xml b/java/com/android/dialer/calldetails/res/layout/ec_data_container.xml new file mode 100644 index 000000000..5ad7912fa --- /dev/null +++ b/java/com/android/dialer/calldetails/res/layout/ec_data_container.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/res/menu/call_details_menu.xml b/java/com/android/dialer/calldetails/res/menu/call_details_menu.xml new file mode 100644 index 000000000..c2d1032da --- /dev/null +++ b/java/com/android/dialer/calldetails/res/menu/call_details_menu.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/res/values/dimens.xml b/java/com/android/dialer/calldetails/res/values/dimens.xml new file mode 100644 index 000000000..b1a8f1c8e --- /dev/null +++ b/java/com/android/dialer/calldetails/res/values/dimens.xml @@ -0,0 +1,40 @@ + + + + 2dp + 16sp + 14sp + + + 16dp + 40dp + 16dp + 24dp + + + 24dp + 16dp + 14dp + 32dp + + + 12sp + 48dp + 72dp + 40dp + 8dp + \ No newline at end of file diff --git a/java/com/android/dialer/calldetails/res/values/strings.xml b/java/com/android/dialer/calldetails/res/values/strings.xml new file mode 100644 index 000000000..8a7cc4cfc --- /dev/null +++ b/java/com/android/dialer/calldetails/res/values/strings.xml @@ -0,0 +1,42 @@ + + + + + Call details + + + Delete + + + Copy number + + + Edit number before call + + + Call + + + Sent a photo + + + Received a photo + + + \"%1$s\" + diff --git a/java/com/android/dialer/calldetails/res/values/styles.xml b/java/com/android/dialer/calldetails/res/values/styles.xml new file mode 100644 index 000000000..4fffe1afb --- /dev/null +++ b/java/com/android/dialer/calldetails/res/values/styles.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/callintent/nano/CallInitiationType.java b/java/com/android/dialer/callintent/nano/CallInitiationType.java index 4badd6e57..1dddb6ce8 100644 --- a/java/com/android/dialer/callintent/nano/CallInitiationType.java +++ b/java/com/android/dialer/callintent/nano/CallInitiationType.java @@ -11,17 +11,19 @@ * 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 + * limitations under the License. */ // Generated by the protocol buffer compiler. DO NOT EDIT! package com.android.dialer.callintent.nano; +/** This file is autogenerated, but javadoc required. */ @SuppressWarnings("hiding") -public final class CallInitiationType extends - com.google.protobuf.nano.ExtendableMessageNano { +public final class CallInitiationType + extends com.google.protobuf.nano.ExtendableMessageNano { + /** This file is autogenerated, but javadoc required. */ // enum Type public interface Type { public static final int UNKNOWN_INITIATION = 0; @@ -44,11 +46,11 @@ public final class CallInitiationType extends } private static volatile CallInitiationType[] _emptyArray; + public static CallInitiationType[] emptyArray() { // Lazily initializes the empty array if (_emptyArray == null) { - synchronized ( - com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { if (_emptyArray == null) { _emptyArray = new CallInitiationType[0]; } @@ -70,20 +72,20 @@ public final class CallInitiationType extends } @Override - public CallInitiationType mergeFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public CallInitiationType mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { while (true) { int tag = input.readTag(); switch (tag) { case 0: return this; - default: { - if (!super.storeUnknownField(input, tag)) { - return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; } - break; - } } } } @@ -94,8 +96,7 @@ public final class CallInitiationType extends } public static CallInitiationType parseFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) - throws java.io.IOException { + com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { return new CallInitiationType().mergeFrom(input); } } diff --git a/java/com/android/dialer/calllogutils/AndroidManifest.xml b/java/com/android/dialer/calllogutils/AndroidManifest.xml new file mode 100644 index 000000000..228865a38 --- /dev/null +++ b/java/com/android/dialer/calllogutils/AndroidManifest.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/java/com/android/dialer/calllogutils/CallEntryFormatter.java b/java/com/android/dialer/calllogutils/CallEntryFormatter.java new file mode 100644 index 000000000..bd6d53f48 --- /dev/null +++ b/java/com/android/dialer/calllogutils/CallEntryFormatter.java @@ -0,0 +1,113 @@ +/* + * 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.calllogutils; + +import android.content.Context; +import android.icu.lang.UCharacter; +import android.icu.text.BreakIterator; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.text.format.DateUtils; +import android.text.format.Formatter; +import com.android.dialer.util.DialerUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** Utility class for formatting data and data usage in call log entries. */ +public class CallEntryFormatter { + + /** + * Formats the provided date into a value suitable for display in the current locale. + * + *

For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 + * may 25,20:02". + * + *

For pre-N devices, the returned value may not start with a capital if the local convention + * is to not capitalize day names. On N+ devices, the returned value is always capitalized. + */ + public static CharSequence formatDate(Context context, long callDateMillis) { + CharSequence dateValue = + DateUtils.formatDateRange( + context, + callDateMillis /* startDate */, + callDateMillis /* endDate */, + DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_WEEKDAY + | DateUtils.FORMAT_SHOW_YEAR); + + // We want the beginning of the date string to be capitalized, even if the word at the beginning + // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” + // (not capitalized). To handle this issue we apply title casing to the start of the sentence so + // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". + // + // The ICU library was not available in Android until N, so we can only do this in N+ devices. + // Pre-N devices will still see incorrect capitalization in some languages. + if (VERSION.SDK_INT < VERSION_CODES.N) { + return dateValue; + } + + // Using the ICU library is safer than just applying toUpperCase() on the first letter of the + // word because in some languages, there can be multiple starting characters which should be + // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be + // capitalized together. + + // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized like the + // month ("May") are not lower-cased as part of the conversion. + return UCharacter.toTitleCase( + Locale.getDefault(), + dateValue.toString(), + BreakIterator.getSentenceInstance(), + UCharacter.TITLECASE_NO_LOWERCASE); + } + + private static CharSequence formatDuration(Context context, long elapsedSeconds) { + long minutes = 0; + long seconds = 0; + + if (elapsedSeconds >= 60) { + minutes = elapsedSeconds / 60; + elapsedSeconds -= minutes * 60; + seconds = elapsedSeconds; + return context.getString(R.string.call_details_duration_format, minutes, seconds); + } else { + seconds = elapsedSeconds; + return context.getString(R.string.call_details_short_duration_format, seconds); + } + } + + /** + * Formats a string containing the call duration and the data usage (if specified). + * + * @param elapsedSeconds Total elapsed seconds. + * @param dataUsage Data usage in bytes, or null if not specified. + * @return String containing call duration and data usage. + */ + public static CharSequence formatDurationAndDataUsage( + Context context, long elapsedSeconds, Long dataUsage) { + CharSequence duration = formatDuration(context, elapsedSeconds); + List durationItems = new ArrayList<>(); + if (dataUsage != null) { + durationItems.add(duration); + durationItems.add(Formatter.formatShortFileSize(context, dataUsage)); + return DialerUtils.join(durationItems); + } else { + return duration; + } + } +} diff --git a/java/com/android/dialer/calllogutils/CallTypeHelper.java b/java/com/android/dialer/calllogutils/CallTypeHelper.java new file mode 100644 index 000000000..d3b5b67d7 --- /dev/null +++ b/java/com/android/dialer/calllogutils/CallTypeHelper.java @@ -0,0 +1,135 @@ +/* + * 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.calllogutils; + +import android.content.res.Resources; +import com.android.dialer.compat.AppCompatConstants; + +/** Helper class to perform operations related to call types. */ +public class CallTypeHelper { + + /** Name used to identify incoming calls. */ + private final CharSequence mIncomingName; + /** Name used to identify incoming calls which were transferred to another device. */ + private final CharSequence mIncomingPulledName; + /** Name used to identify outgoing calls. */ + private final CharSequence mOutgoingName; + /** Name used to identify outgoing calls which were transferred to another device. */ + private final CharSequence mOutgoingPulledName; + /** Name used to identify missed calls. */ + private final CharSequence mMissedName; + /** Name used to identify incoming video calls. */ + private final CharSequence mIncomingVideoName; + /** Name used to identify incoming video calls which were transferred to another device. */ + private final CharSequence mIncomingVideoPulledName; + /** Name used to identify outgoing video calls. */ + private final CharSequence mOutgoingVideoName; + /** Name used to identify outgoing video calls which were transferred to another device. */ + private final CharSequence mOutgoingVideoPulledName; + /** Name used to identify missed video calls. */ + private final CharSequence mMissedVideoName; + /** Name used to identify voicemail calls. */ + private final CharSequence mVoicemailName; + /** Name used to identify rejected calls. */ + private final CharSequence mRejectedName; + /** Name used to identify blocked calls. */ + private final CharSequence mBlockedName; + /** Name used to identify calls which were answered on another device. */ + private final CharSequence mAnsweredElsewhereName; + + public CallTypeHelper(Resources resources) { + // Cache these values so that we do not need to look them up each time. + mIncomingName = resources.getString(R.string.type_incoming); + mIncomingPulledName = resources.getString(R.string.type_incoming_pulled); + mOutgoingName = resources.getString(R.string.type_outgoing); + mOutgoingPulledName = resources.getString(R.string.type_outgoing_pulled); + mMissedName = resources.getString(R.string.type_missed); + mIncomingVideoName = resources.getString(R.string.type_incoming_video); + mIncomingVideoPulledName = resources.getString(R.string.type_incoming_video_pulled); + mOutgoingVideoName = resources.getString(R.string.type_outgoing_video); + mOutgoingVideoPulledName = resources.getString(R.string.type_outgoing_video_pulled); + mMissedVideoName = resources.getString(R.string.type_missed_video); + mVoicemailName = resources.getString(R.string.type_voicemail); + mRejectedName = resources.getString(R.string.type_rejected); + mBlockedName = resources.getString(R.string.type_blocked); + mAnsweredElsewhereName = resources.getString(R.string.type_answered_elsewhere); + } + + public static boolean isMissedCallType(int callType) { + return (callType != AppCompatConstants.CALLS_INCOMING_TYPE + && callType != AppCompatConstants.CALLS_OUTGOING_TYPE + && callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE + && callType != AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE); + } + + /** Returns the text used to represent the given call type. */ + public CharSequence getCallTypeText(int callType, boolean isVideoCall, boolean isPulledCall) { + switch (callType) { + case AppCompatConstants.CALLS_INCOMING_TYPE: + if (isVideoCall) { + if (isPulledCall) { + return mIncomingVideoPulledName; + } else { + return mIncomingVideoName; + } + } else { + if (isPulledCall) { + return mIncomingPulledName; + } else { + return mIncomingName; + } + } + + case AppCompatConstants.CALLS_OUTGOING_TYPE: + if (isVideoCall) { + if (isPulledCall) { + return mOutgoingVideoPulledName; + } else { + return mOutgoingVideoName; + } + } else { + if (isPulledCall) { + return mOutgoingPulledName; + } else { + return mOutgoingName; + } + } + + case AppCompatConstants.CALLS_MISSED_TYPE: + if (isVideoCall) { + return mMissedVideoName; + } else { + return mMissedName; + } + + case AppCompatConstants.CALLS_VOICEMAIL_TYPE: + return mVoicemailName; + + case AppCompatConstants.CALLS_REJECTED_TYPE: + return mRejectedName; + + case AppCompatConstants.CALLS_BLOCKED_TYPE: + return mBlockedName; + + case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: + return mAnsweredElsewhereName; + + default: + return mMissedName; + } + } +} diff --git a/java/com/android/dialer/calllogutils/CallTypeIconsView.java b/java/com/android/dialer/calllogutils/CallTypeIconsView.java new file mode 100644 index 000000000..61208bc9a --- /dev/null +++ b/java/com/android/dialer/calllogutils/CallTypeIconsView.java @@ -0,0 +1,244 @@ +/* + * 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.calllogutils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import com.android.contacts.common.util.BitmapUtil; +import com.android.dialer.compat.AppCompatConstants; +import java.util.ArrayList; +import java.util.List; + +/** + * View that draws one or more symbols for different types of calls (missed calls, outgoing etc). + * The symbols are set up horizontally. As this view doesn't create subviews, it is better suited + * for ListView-recycling that a regular LinearLayout using ImageViews. + */ +public class CallTypeIconsView extends View { + + private static Resources sResources; + private List mCallTypes = new ArrayList<>(3); + private boolean mShowVideo = false; + private boolean mShowHd = false; + private int mWidth; + private int mHeight; + + public CallTypeIconsView(Context context) { + this(context, null); + } + + public CallTypeIconsView(Context context, AttributeSet attrs) { + super(context, attrs); + if (sResources == null) { + sResources = new Resources(context); + } + } + + public void clear() { + mCallTypes.clear(); + mWidth = 0; + mHeight = 0; + invalidate(); + } + + public void add(int callType) { + mCallTypes.add(callType); + + final Drawable drawable = getCallTypeDrawable(callType); + mWidth += drawable.getIntrinsicWidth() + sResources.iconMargin; + mHeight = Math.max(mHeight, drawable.getIntrinsicHeight()); + invalidate(); + } + + /** + * Determines whether the video call icon will be shown. + * + * @param showVideo True where the video icon should be shown. + */ + public void setShowVideo(boolean showVideo) { + mShowVideo = showVideo; + if (showVideo) { + mWidth += sResources.videoCall.getIntrinsicWidth(); + mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight()); + invalidate(); + } + } + + /** + * Determines if the video icon should be shown. + * + * @return True if the video icon should be shown. + */ + public boolean isVideoShown() { + return mShowVideo; + } + + public void setShowHd(boolean showHd) { + mShowHd = showHd; + if (showHd) { + mWidth += sResources.hdCall.getIntrinsicWidth(); + mHeight = Math.max(mHeight, sResources.hdCall.getIntrinsicHeight()); + invalidate(); + } + } + + public int getCount() { + return mCallTypes.size(); + } + + public int getCallType(int index) { + return mCallTypes.get(index); + } + + private Drawable getCallTypeDrawable(int callType) { + switch (callType) { + case AppCompatConstants.CALLS_INCOMING_TYPE: + case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE: + return sResources.incoming; + case AppCompatConstants.CALLS_OUTGOING_TYPE: + return sResources.outgoing; + case AppCompatConstants.CALLS_MISSED_TYPE: + return sResources.missed; + case AppCompatConstants.CALLS_VOICEMAIL_TYPE: + return sResources.voicemail; + case AppCompatConstants.CALLS_BLOCKED_TYPE: + return sResources.blocked; + default: + // It is possible for users to end up with calls with unknown call types in their + // call history, possibly due to 3rd party call log implementations (e.g. to + // distinguish between rejected and missed calls). Instead of crashing, just + // assume that all unknown call types are missed calls. + return sResources.missed; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mWidth, mHeight); + } + + @Override + protected void onDraw(Canvas canvas) { + int left = 0; + for (Integer callType : mCallTypes) { + final Drawable drawable = getCallTypeDrawable(callType); + final int right = left + drawable.getIntrinsicWidth(); + drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight()); + drawable.draw(canvas); + left = right + sResources.iconMargin; + } + + // If showing the video call icon, draw it scaled appropriately. + if (mShowVideo) { + final Drawable drawable = sResources.videoCall; + final int right = left + sResources.videoCall.getIntrinsicWidth(); + drawable.setBounds(left, 0, right, sResources.videoCall.getIntrinsicHeight()); + drawable.draw(canvas); + } + // If showing HD call icon, draw it scaled appropriately. + if (mShowHd) { + final Drawable drawable = sResources.hdCall; + final int right = left + sResources.hdCall.getIntrinsicWidth(); + drawable.setBounds(left, 0, right, sResources.hdCall.getIntrinsicHeight()); + drawable.draw(canvas); + } + } + + private static class Resources { + + // Drawable representing an incoming answered call. + public final Drawable incoming; + + // Drawable respresenting an outgoing call. + public final Drawable outgoing; + + // Drawable representing an incoming missed call. + public final Drawable missed; + + // Drawable representing a voicemail. + public final Drawable voicemail; + + // Drawable representing a blocked call. + public final Drawable blocked; + + // Drawable repesenting a video call. + public final Drawable videoCall; + + // Drawable represeting a hd call. + public final Drawable hdCall; + + /** The margin to use for icons. */ + public final int iconMargin; + + /** + * Configures the call icon drawables. A single white call arrow which points down and left is + * used as a basis for all of the call arrow icons, applying rotation and colors as needed. + * + * @param context The current context. + */ + public Resources(Context context) { + final android.content.res.Resources r = context.getResources(); + + incoming = r.getDrawable(R.drawable.ic_call_arrow); + incoming.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); + + // Create a rotated instance of the call arrow for outgoing calls. + outgoing = BitmapUtil.getRotatedDrawable(r, R.drawable.ic_call_arrow, 180f); + outgoing.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY); + + // Need to make a copy of the arrow drawable, otherwise the same instance colored + // above will be recolored here. + missed = r.getDrawable(R.drawable.ic_call_arrow).mutate(); + missed.setColorFilter(r.getColor(R.color.missed_call), PorterDuff.Mode.MULTIPLY); + + voicemail = r.getDrawable(R.drawable.quantum_ic_voicemail_white_18); + voicemail.setColorFilter( + r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); + + blocked = getScaledBitmap(context, R.drawable.ic_block_24dp); + blocked.setColorFilter(r.getColor(R.color.blocked_call), PorterDuff.Mode.MULTIPLY); + + videoCall = getScaledBitmap(context, R.drawable.quantum_ic_videocam_white_24); + videoCall.setColorFilter( + r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); + + hdCall = getScaledBitmap(context, R.drawable.quantum_ic_hd_white_24); + hdCall.setColorFilter( + r.getColor(R.color.dialer_secondary_text_color), PorterDuff.Mode.MULTIPLY); + + iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin); + } + + // Gets the icon, scaled to the height of the call type icons. This helps display all the + // icons to be the same height, while preserving their width aspect ratio. + private Drawable getScaledBitmap(Context context, int resourceId) { + Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resourceId); + int scaledHeight = context.getResources().getDimensionPixelSize(R.dimen.call_type_icon_size); + int scaledWidth = + (int) ((float) icon.getWidth() * ((float) scaledHeight / (float) icon.getHeight())); + Bitmap scaledIcon = Bitmap.createScaledBitmap(icon, scaledWidth, scaledHeight, false); + return new BitmapDrawable(context.getResources(), scaledIcon); + } + } +} diff --git a/java/com/android/dialer/calllogutils/PhoneAccountUtils.java b/java/com/android/dialer/calllogutils/PhoneAccountUtils.java new file mode 100644 index 000000000..c639893ef --- /dev/null +++ b/java/com/android/dialer/calllogutils/PhoneAccountUtils.java @@ -0,0 +1,104 @@ +/* + * 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.calllogutils; + +import android.content.ComponentName; +import android.content.Context; +import android.support.annotation.Nullable; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.text.TextUtils; +import com.android.dialer.telecom.TelecomUtil; +import java.util.ArrayList; +import java.util.List; + +/** Methods to help extract {@code PhoneAccount} information from database and Telecomm sources. */ +public class PhoneAccountUtils { + + /** Return a list of phone accounts that are subscription/SIM accounts. */ + public static List getSubscriptionPhoneAccounts(Context context) { + List subscriptionAccountHandles = new ArrayList(); + final List accountHandles = + TelecomUtil.getCallCapablePhoneAccounts(context); + for (PhoneAccountHandle accountHandle : accountHandles) { + PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); + if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { + subscriptionAccountHandles.add(accountHandle); + } + } + return subscriptionAccountHandles; + } + + /** Compose PhoneAccount object from component name and account id. */ + @Nullable + public static PhoneAccountHandle getAccount( + @Nullable String componentString, @Nullable String accountId) { + if (TextUtils.isEmpty(componentString) || TextUtils.isEmpty(accountId)) { + return null; + } + final ComponentName componentName = ComponentName.unflattenFromString(componentString); + if (componentName == null) { + return null; + } + return new PhoneAccountHandle(componentName, accountId); + } + + /** Extract account label from PhoneAccount object. */ + @Nullable + public static String getAccountLabel( + Context context, @Nullable PhoneAccountHandle accountHandle) { + PhoneAccount account = getAccountOrNull(context, accountHandle); + if (account != null && account.getLabel() != null) { + return account.getLabel().toString(); + } + return null; + } + + /** Extract account color from PhoneAccount object. */ + public static int getAccountColor(Context context, @Nullable PhoneAccountHandle accountHandle) { + final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); + + // For single-sim devices the PhoneAccount will be NO_HIGHLIGHT_COLOR by default, so it is + // safe to always use the account highlight color. + return account == null ? PhoneAccount.NO_HIGHLIGHT_COLOR : account.getHighlightColor(); + } + + /** + * Determine whether a phone account supports call subjects. + * + * @return {@code true} if call subjects are supported, {@code false} otherwise. + */ + public static boolean getAccountSupportsCallSubject( + Context context, @Nullable PhoneAccountHandle accountHandle) { + final PhoneAccount account = TelecomUtil.getPhoneAccount(context, accountHandle); + + return account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT); + } + + /** + * Retrieve the account metadata, but if the account does not exist or the device has only a + * single registered and enabled account, return null. + */ + @Nullable + private static PhoneAccount getAccountOrNull( + Context context, @Nullable PhoneAccountHandle accountHandle) { + if (TelecomUtil.getCallCapablePhoneAccounts(context).size() <= 1) { + return null; + } + return TelecomUtil.getPhoneAccount(context, accountHandle); + } +} diff --git a/java/com/android/dialer/calllogutils/PhoneCallDetails.java b/java/com/android/dialer/calllogutils/PhoneCallDetails.java new file mode 100644 index 000000000..ba05a87e2 --- /dev/null +++ b/java/com/android/dialer/calllogutils/PhoneCallDetails.java @@ -0,0 +1,206 @@ +/* + * 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.calllogutils; + +import android.content.Context; +import android.content.res.Resources; +import android.net.Uri; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; +import android.text.TextUtils; +import com.android.contacts.common.ContactsUtils.UserType; +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.phonenumbercache.ContactInfo; + +/** The details of a phone call to be shown in the UI. */ +public class PhoneCallDetails { + + // The number of the other party involved in the call. + public CharSequence number; + // Post-dial digits associated with the outgoing call. + public String postDialDigits; + // The secondary line number the call was received via. + public String viaNumber; + // The number presenting rules set by the network, e.g., {@link Calls#PRESENTATION_ALLOWED} + public int numberPresentation; + // The country corresponding with the phone number. + public String countryIso; + // The geocoded location for the phone number. + public String geocode; + + /** + * The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}. + * + *

There might be multiple types if this represents a set of entries grouped together. + */ + public int[] callTypes; + + // The date of the call, in milliseconds since the epoch. + public long date; + // The duration of the call in milliseconds, or 0 for missed calls. + public long duration; + // The name of the contact, or the empty string. + public CharSequence namePrimary; + // The alternative name of the contact, e.g. last name first, or the empty string + public CharSequence nameAlternative; + /** + * The user's preference on name display order, last name first or first time first. {@see + * ContactsPreferences} + */ + public int nameDisplayOrder; + // The type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available. + public int numberType; + // The custom label associated with the phone number in the contact, or the empty string. + public CharSequence numberLabel; + // The URI of the contact associated with this phone call. + public Uri contactUri; + + /** + * The photo URI of the picture of the contact that is associated with this phone call or null if + * there is none. + * + *

This is meant to store the high-res photo only. + */ + public Uri photoUri; + + // The source type of the contact associated with this call. + public int sourceType; + + // The object id type of the contact associated with this call. + public String objectId; + + // The unique identifier for the account associated with the call. + public PhoneAccountHandle accountHandle; + + // Features applicable to this call. + public int features; + + // Total data usage for this call. + public Long dataUsage; + + // Voicemail transcription + public String transcription; + + // The display string for the number. + public String displayNumber; + + // Whether the contact number is a voicemail number. + public boolean isVoicemail; + + /** The {@link UserType} of the contact */ + public @UserType long contactUserType; + + /** + * If this is a voicemail, whether the message is read. For other types of calls, this defaults to + * {@code true}. + */ + public boolean isRead = true; + + // If this call is a spam number. + public boolean isSpam = false; + + // If this call is a blocked number. + public boolean isBlocked = false; + + // Call location and date text. + public CharSequence callLocationAndDate; + + // Call description. + public CharSequence callDescription; + public String accountComponentName; + public String accountId; + public ContactInfo cachedContactInfo; + public int voicemailId; + public int previousGroup; + + /** + * Constructor with required fields for the details of a call with a number associated with a + * contact. + */ + public PhoneCallDetails( + CharSequence number, int numberPresentation, CharSequence postDialDigits) { + this.number = number; + this.numberPresentation = numberPresentation; + this.postDialDigits = postDialDigits.toString(); + } + /** + * Construct the "on {accountLabel} via {viaNumber}" accessibility description for the account + * list item, depending on the existence of the accountLabel and viaNumber. + * + * @param viaNumber The number that this call is being placed via. + * @param accountLabel The {@link PhoneAccount} label that this call is being placed with. + * @return The description of the account that this call has been placed on. + */ + public static CharSequence createAccountLabelDescription( + Resources resources, @Nullable String viaNumber, @Nullable CharSequence accountLabel) { + + if ((!TextUtils.isEmpty(viaNumber)) && !TextUtils.isEmpty(accountLabel)) { + String msg = + resources.getString( + R.string.description_via_number_phone_account, accountLabel, viaNumber); + CharSequence accountNumberLabel = + ContactDisplayUtils.getTelephoneTtsSpannable(msg, viaNumber); + return (accountNumberLabel == null) ? msg : accountNumberLabel; + } else if (!TextUtils.isEmpty(viaNumber)) { + CharSequence viaNumberLabel = + ContactDisplayUtils.getTtsSpannedPhoneNumber( + resources, R.string.description_via_number, viaNumber); + return (viaNumberLabel == null) ? viaNumber : viaNumberLabel; + } else if (!TextUtils.isEmpty(accountLabel)) { + return TextUtils.expandTemplate( + resources.getString(R.string.description_phone_account), accountLabel); + } + return ""; + } + + /** + * Returns the preferred name for the call details as specified by the {@link #nameDisplayOrder} + * + * @return the preferred name + */ + public CharSequence getPreferredName() { + if (nameDisplayOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY + || TextUtils.isEmpty(nameAlternative)) { + return namePrimary; + } + return nameAlternative; + } + + public void updateDisplayNumber( + Context context, CharSequence formattedNumber, boolean isVoicemail) { + displayNumber = + PhoneNumberDisplayUtil.getDisplayNumber( + context, number, numberPresentation, formattedNumber, postDialDigits, isVoicemail) + .toString(); + } + + public boolean hasIncomingCalls() { + for (int i = 0; i < callTypes.length; i++) { + if (callTypes[i] == CallLog.Calls.INCOMING_TYPE + || callTypes[i] == CallLog.Calls.MISSED_TYPE + || callTypes[i] == CallLog.Calls.VOICEMAIL_TYPE + || callTypes[i] == CallLog.Calls.REJECTED_TYPE + || callTypes[i] == CallLog.Calls.BLOCKED_TYPE) { + return true; + } + } + return false; + } +} diff --git a/java/com/android/dialer/calllogutils/PhoneNumberDisplayUtil.java b/java/com/android/dialer/calllogutils/PhoneNumberDisplayUtil.java new file mode 100644 index 000000000..9bebfacac --- /dev/null +++ b/java/com/android/dialer/calllogutils/PhoneNumberDisplayUtil.java @@ -0,0 +1,83 @@ +/* + * 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.calllogutils; + +import android.content.Context; +import android.provider.CallLog.Calls; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; +import android.text.TextUtils; +import com.android.contacts.common.compat.PhoneNumberUtilsCompat; +import com.android.dialer.phonenumberutil.PhoneNumberHelper; + +/** Helper for formatting and managing the display of phone numbers. */ +public class PhoneNumberDisplayUtil { + + /** Returns the string to display for the given phone number if there is no matching contact. */ + public static CharSequence getDisplayName( + Context context, CharSequence number, int presentation, boolean isVoicemail) { + if (presentation == Calls.PRESENTATION_UNKNOWN) { + return context.getResources().getString(R.string.unknown); + } + if (presentation == Calls.PRESENTATION_RESTRICTED) { + return PhoneNumberHelper.getDisplayNameForRestrictedNumber(context); + } + if (presentation == Calls.PRESENTATION_PAYPHONE) { + return context.getResources().getString(R.string.payphone); + } + if (isVoicemail) { + return context.getResources().getString(R.string.voicemail_string); + } + if (PhoneNumberHelper.isLegacyUnknownNumbers(number)) { + return context.getResources().getString(R.string.unknown); + } + return ""; + } + + /** + * Returns the string to display for the given phone number. + * + * @param number the number to display + * @param formattedNumber the formatted number if available, may be null + */ + public static CharSequence getDisplayNumber( + Context context, + CharSequence number, + int presentation, + CharSequence formattedNumber, + CharSequence postDialDigits, + boolean isVoicemail) { + final CharSequence displayName = getDisplayName(context, number, presentation, isVoicemail); + if (!TextUtils.isEmpty(displayName)) { + return getTtsSpannableLtrNumber(displayName); + } + + if (!TextUtils.isEmpty(formattedNumber)) { + return getTtsSpannableLtrNumber(formattedNumber); + } else if (!TextUtils.isEmpty(number)) { + return getTtsSpannableLtrNumber(number.toString() + postDialDigits); + } else { + return context.getResources().getString(R.string.unknown); + } + } + + /** Returns number annotated as phone number in LTR direction. */ + public static CharSequence getTtsSpannableLtrNumber(CharSequence number) { + return PhoneNumberUtilsCompat.createTtsSpannable( + BidiFormatter.getInstance().unicodeWrap(number.toString(), TextDirectionHeuristics.LTR)); + } +} diff --git a/java/com/android/dialer/calllogutils/res/values/colors.xml b/java/com/android/dialer/calllogutils/res/values/colors.xml new file mode 100644 index 000000000..dc4ec2493 --- /dev/null +++ b/java/com/android/dialer/calllogutils/res/values/colors.xml @@ -0,0 +1,24 @@ + + + + + #ff2e58 + + #00c853 + + @color/dialer_secondary_text_color + \ No newline at end of file diff --git a/java/com/android/dialer/calllogutils/res/values/dimens.xml b/java/com/android/dialer/calllogutils/res/values/dimens.xml new file mode 100644 index 000000000..0935ac188 --- /dev/null +++ b/java/com/android/dialer/calllogutils/res/values/dimens.xml @@ -0,0 +1,19 @@ + + + + 12dp + \ No newline at end of file diff --git a/java/com/android/dialer/calllogutils/res/values/strings.xml b/java/com/android/dialer/calllogutils/res/values/strings.xml new file mode 100644 index 000000000..6a6f10113 --- /dev/null +++ b/java/com/android/dialer/calllogutils/res/values/strings.xml @@ -0,0 +1,90 @@ + + + + + Incoming call + + + Incoming call transferred to another device + + + Outgoing call + + + Outgoing call transferred to another device + + + Missed call + + + Incoming video call + + + Incoming video call transferred to another device + + + Outgoing video call + + + Outgoing video call transferred to another device + + + Missed video call + + + Voicemail + + + Declined call + + + Blocked call + + + Call answered on another device + + + on ^1 + + + via %1$s + + + on %1$s, via %2$s + + + Voicemail + + + %s min %s sec + + + %s sec + \ No newline at end of file diff --git a/java/com/android/dialer/common/Assert.java b/java/com/android/dialer/common/Assert.java index 00b4f2595..943e1ddcf 100644 --- a/java/com/android/dialer/common/Assert.java +++ b/java/com/android/dialer/common/Assert.java @@ -19,6 +19,7 @@ package com.android.dialer.common; import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import javax.annotation.CheckReturnValue; /** Assertions which will result in program termination unless disabled by flags. */ public class Assert { @@ -33,7 +34,9 @@ public class Assert { * Called when a truly exceptional case occurs. * * @throws AssertionError + * @deprecated Use throw Assert.create*FailException() instead. */ + @Deprecated public static void fail() { throw new AssertionError("Fail"); } @@ -43,11 +46,38 @@ public class Assert { * * @param reason the optional reason to supply as the exception message * @throws AssertionError + * @deprecated Use throw Assert.create*FailException() instead. */ + @Deprecated public static void fail(String reason) { throw new AssertionError(reason); } + @CheckReturnValue + public static AssertionError createAssertionFailException(String msg) { + return new AssertionError(msg); + } + + @CheckReturnValue + public static UnsupportedOperationException createUnsupportedOperationFailException() { + return new UnsupportedOperationException(); + } + + @CheckReturnValue + public static UnsupportedOperationException createUnsupportedOperationFailException(String msg) { + return new UnsupportedOperationException(msg); + } + + @CheckReturnValue + public static IllegalStateException createIllegalStateFailException() { + return new IllegalStateException(); + } + + @CheckReturnValue + public static IllegalStateException createIllegalStateFailException(String msg) { + return new IllegalStateException(msg); + } + /** * Ensures the truth of an expression involving one or more parameters to the calling method. * diff --git a/java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java b/java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java deleted file mode 100644 index f9d7cea90..000000000 --- a/java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.common; - -import android.support.annotation.Nullable; -import javax.annotation.Generated; - - - final class AutoValue_FallibleAsyncTask_FallibleTaskResult extends FallibleAsyncTask.FallibleTaskResult { - - private final Throwable throwable; - private final ResultT result; - - AutoValue_FallibleAsyncTask_FallibleTaskResult( - @Nullable Throwable throwable, - @Nullable ResultT result) { - this.throwable = throwable; - this.result = result; - } - - @Nullable - @Override - public Throwable getThrowable() { - return throwable; - } - - @Nullable - @Override - public ResultT getResult() { - return result; - } - - @Override - public String toString() { - return "FallibleTaskResult{" - + "throwable=" + throwable + ", " - + "result=" + result - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof FallibleAsyncTask.FallibleTaskResult) { - FallibleAsyncTask.FallibleTaskResult that = (FallibleAsyncTask.FallibleTaskResult) o; - return ((this.throwable == null) ? (that.getThrowable() == null) : this.throwable.equals(that.getThrowable())) - && ((this.result == null) ? (that.getResult() == null) : this.result.equals(that.getResult())); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (throwable == null) ? 0 : this.throwable.hashCode(); - h *= 1000003; - h ^= (result == null) ? 0 : this.result.hashCode(); - return h; - } - -} - diff --git a/java/com/android/dialer/common/FallibleAsyncTask.java b/java/com/android/dialer/common/FallibleAsyncTask.java index fbdbda75f..f3abace1a 100644 --- a/java/com/android/dialer/common/FallibleAsyncTask.java +++ b/java/com/android/dialer/common/FallibleAsyncTask.java @@ -20,7 +20,7 @@ import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.dialer.common.FallibleAsyncTask.FallibleTaskResult; - +import com.google.auto.value.AutoValue; /** * A task that runs work in the background, passing Throwables from {@link @@ -52,8 +52,8 @@ public abstract class FallibleAsyncTask * * @param the type of the result of the background computation */ - - protected abstract static class FallibleTaskResult { + @AutoValue + public abstract static class FallibleTaskResult { /** Creates an instance of FallibleTaskResult for the given throwable. */ private static FallibleTaskResult createFailureResult(@NonNull Throwable t) { diff --git a/java/com/android/dialer/common/PerAccountSharedPreferences.java b/java/com/android/dialer/common/PerAccountSharedPreferences.java new file mode 100644 index 000000000..0ed1b03a5 --- /dev/null +++ b/java/com/android/dialer/common/PerAccountSharedPreferences.java @@ -0,0 +1,146 @@ +/* + * 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.common; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; +import java.util.Set; + +/** + * Class that helps us store dialer preferences that are phone account dependent. This is necessary + * for cases such as settings that are phone account dependent e.g endless vm. The logic is to + * essentially store the shared preference by appending the phone account id to the key. + */ +public class PerAccountSharedPreferences { + private final String sharedPrefsKeyPrefix; + private final SharedPreferences preferences; + private final PhoneAccountHandle phoneAccountHandle; + + public PerAccountSharedPreferences( + Context context, PhoneAccountHandle handle, SharedPreferences prefs) { + preferences = prefs; + phoneAccountHandle = handle; + sharedPrefsKeyPrefix = "phone_account_dependent_"; + } + + /** + * Not to be used, currently only used by {@VisualVoicemailPreferences} for legacy reasons. + */ + protected PerAccountSharedPreferences( + Context context, PhoneAccountHandle handle, SharedPreferences prefs, String prefix) { + Assert.checkArgument(prefix.equals("visual_voicemail_")); + preferences = prefs; + phoneAccountHandle = handle; + sharedPrefsKeyPrefix = prefix; + } + + public class Editor { + + private final SharedPreferences.Editor editor; + + private Editor() { + editor = preferences.edit(); + } + + public void apply() { + editor.apply(); + } + + public Editor putBoolean(String key, boolean value) { + editor.putBoolean(getKey(key), value); + return this; + } + + public Editor putFloat(String key, float value) { + editor.putFloat(getKey(key), value); + return this; + } + + public Editor putInt(String key, int value) { + editor.putInt(getKey(key), value); + return this; + } + + public Editor putLong(String key, long value) { + editor.putLong(getKey(key), value); + return this; + } + + public Editor putString(String key, String value) { + editor.putString(getKey(key), value); + return this; + } + + public Editor putStringSet(String key, Set value) { + editor.putStringSet(getKey(key), value); + return this; + } + } + + public Editor edit() { + return new Editor(); + } + + public boolean getBoolean(String key, boolean defValue) { + return getValue(key, defValue); + } + + public float getFloat(String key, float defValue) { + return getValue(key, defValue); + } + + public int getInt(String key, int defValue) { + return getValue(key, defValue); + } + + public long getLong(String key, long defValue) { + return getValue(key, defValue); + } + + public String getString(String key, String defValue) { + return getValue(key, defValue); + } + + @Nullable + public String getString(String key) { + return getValue(key, null); + } + + public Set getStringSet(String key, Set defValue) { + return getValue(key, defValue); + } + + public boolean contains(String key) { + return preferences.contains(getKey(key)); + } + + private T getValue(String key, T defValue) { + if (!contains(key)) { + return defValue; + } + Object object = preferences.getAll().get(getKey(key)); + if (object == null) { + return defValue; + } + return (T) object; + } + + private String getKey(String key) { + return sharedPrefsKeyPrefix + key + "_" + phoneAccountHandle.getId(); + } +} diff --git a/java/com/android/dialer/common/proguard.flags b/java/com/android/dialer/common/proguard.flags new file mode 100644 index 000000000..4b6b84671 --- /dev/null +++ b/java/com/android/dialer/common/proguard.flags @@ -0,0 +1,4 @@ +-assumenosideeffects class com.android.dialer.common.LogUtil { + public static void v(...); + public static void d(...); +} diff --git a/java/com/android/dialer/common/res/values/config.xml b/java/com/android/dialer/common/res/values/config.xml new file mode 100644 index 000000000..c4df279ba --- /dev/null +++ b/java/com/android/dialer/common/res/values/config.xml @@ -0,0 +1,4 @@ + + + false + \ No newline at end of file diff --git a/java/com/android/dialer/constants/Constants.java b/java/com/android/dialer/constants/Constants.java index 77773018a..d92c0bcfc 100644 --- a/java/com/android/dialer/constants/Constants.java +++ b/java/com/android/dialer/constants/Constants.java @@ -19,7 +19,6 @@ package com.android.dialer.constants; import android.support.annotation.NonNull; import com.android.dialer.common.Assert; import com.android.dialer.proguard.UsedByReflection; -import com.android.dialer.constants.ConstantsImpl; /** * Utility to access constants that are different across build variants (Google Dialer, AOSP, @@ -29,11 +28,22 @@ import com.android.dialer.constants.ConstantsImpl; */ @UsedByReflection(value = "Constants.java") public abstract class Constants { - private static Constants instance = new ConstantsImpl(); + private static Constants instance; private static boolean didInitializeInstance; @NonNull public static synchronized Constants get() { + if (!didInitializeInstance) { + didInitializeInstance = true; + try { + Class clazz = Class.forName(Constants.class.getName() + "Impl"); + instance = (Constants) clazz.getConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + Assert.fail( + "Unable to create an instance of ConstantsImpl. To fix this error include one of the " + + "constants modules (googledialer, aosp etc...) in your target."); + } + } return instance; } diff --git a/java/com/android/dialer/database/CallLogQueryHandler.java b/java/com/android/dialer/database/CallLogQueryHandler.java index ffca69f40..1f6bd5fb3 100644 --- a/java/com/android/dialer/database/CallLogQueryHandler.java +++ b/java/com/android/dialer/database/CallLogQueryHandler.java @@ -33,6 +33,7 @@ import android.os.Message; import android.provider.CallLog.Calls; import android.provider.VoicemailContract.Status; import android.provider.VoicemailContract.Voicemails; +import android.support.v4.os.BuildCompat; import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.AppCompatConstants; @@ -40,6 +41,7 @@ import com.android.dialer.compat.SdkVersionOverride; import com.android.dialer.phonenumbercache.CallLogQuery; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; +import com.android.voicemail.VoicemailComponent; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -126,13 +128,23 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { public void fetchVoicemailUnreadCount() { if (TelecomUtil.hasReadWriteVoicemailPermissions(mContext)) { // Only count voicemails that have not been read and have not been deleted. + StringBuilder where = + new StringBuilder(Voicemails.IS_READ + "=0" + " AND " + Voicemails.DELETED + "=0 "); + List selectionArgs = new ArrayList<>(); + + if (BuildCompat.isAtLeastO()) { + VoicemailComponent.get(mContext) + .getVoicemailClient() + .appendOmtpVoicemailSelectionClause(mContext, where, selectionArgs); + } + startQuery( QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN, null, Voicemails.CONTENT_URI, new String[] {Voicemails._ID}, - Voicemails.IS_READ + "=0" + " AND " + Voicemails.DELETED + "=0", - null, + where.toString(), + selectionArgs.toArray(new String[selectionArgs.size()]), null); } } @@ -168,6 +180,12 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { selectionArgs.add(Long.toString(newerThan)); } + if (callType == Calls.VOICEMAIL_TYPE) { + VoicemailComponent.get(mContext) + .getVoicemailClient() + .appendOmtpVoicemailSelectionClause(mContext, where, selectionArgs); + } + final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit; final String selection = where.length() > 0 ? where.toString() : null; Uri uri = diff --git a/java/com/android/dialer/debug/bindings/stub/DebugBindings.java b/java/com/android/dialer/debug/bindings/stub/DebugBindings.java new file mode 100644 index 000000000..7df38341d --- /dev/null +++ b/java/com/android/dialer/debug/bindings/stub/DebugBindings.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 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.debug.bindings; + +import android.content.Context; + +/** Hooks into the debug module. */ +public class DebugBindings { + + public static void registerConnectionService(Context context) {} + + public static void addNewIncomingCall(Context context, String phoneNumber) {} +} diff --git a/java/com/android/dialer/enrichedcall/AutoValue_EnrichedCallCapabilities.java b/java/com/android/dialer/enrichedcall/AutoValue_EnrichedCallCapabilities.java deleted file mode 100644 index 14299f92c..000000000 --- a/java/com/android/dialer/enrichedcall/AutoValue_EnrichedCallCapabilities.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2016 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.enrichedcall; - -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_EnrichedCallCapabilities extends EnrichedCallCapabilities { - - private final boolean supportsCallComposer; - private final boolean supportsPostCall; - - AutoValue_EnrichedCallCapabilities( - boolean supportsCallComposer, - boolean supportsPostCall) { - this.supportsCallComposer = supportsCallComposer; - this.supportsPostCall = supportsPostCall; - } - - @Override - public boolean supportsCallComposer() { - return supportsCallComposer; - } - - @Override - public boolean supportsPostCall() { - return supportsPostCall; - } - - @Override - public String toString() { - return "EnrichedCallCapabilities{" - + "supportsCallComposer=" + supportsCallComposer + ", " - + "supportsPostCall=" + supportsPostCall + ", " - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof EnrichedCallCapabilities) { - EnrichedCallCapabilities that = (EnrichedCallCapabilities) o; - return (this.supportsCallComposer == that.supportsCallComposer()) - && (this.supportsPostCall == that.supportsPostCall()); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= this.supportsCallComposer ? 1231 : 1237; - h *= 1000003; - h ^= this.supportsPostCall ? 1231 : 1237; - return h; - } - -} - diff --git a/java/com/android/dialer/enrichedcall/AutoValue_OutgoingCallComposerData.java b/java/com/android/dialer/enrichedcall/AutoValue_OutgoingCallComposerData.java deleted file mode 100644 index edfefc479..000000000 --- a/java/com/android/dialer/enrichedcall/AutoValue_OutgoingCallComposerData.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2016 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.enrichedcall; - -import android.net.Uri; -import android.support.annotation.Nullable; -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_OutgoingCallComposerData extends OutgoingCallComposerData { - - private final String subject; - private final Uri imageUri; - private final String imageContentType; - - private AutoValue_OutgoingCallComposerData( - @Nullable String subject, - @Nullable Uri imageUri, - @Nullable String imageContentType) { - this.subject = subject; - this.imageUri = imageUri; - this.imageContentType = imageContentType; - } - - @Nullable - @Override - public String getSubject() { - return subject; - } - - @Nullable - @Override - public Uri getImageUri() { - return imageUri; - } - - @Nullable - @Override - public String getImageContentType() { - return imageContentType; - } - - @Override - public String toString() { - return "OutgoingCallComposerData{" - + "subject=" + subject + ", " - + "imageUri=" + imageUri + ", " - + "imageContentType=" + imageContentType - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof OutgoingCallComposerData) { - OutgoingCallComposerData that = (OutgoingCallComposerData) o; - return ((this.subject == null) ? (that.getSubject() == null) : this.subject.equals(that.getSubject())) - && ((this.imageUri == null) ? (that.getImageUri() == null) : this.imageUri.equals(that.getImageUri())) - && ((this.imageContentType == null) ? (that.getImageContentType() == null) : this.imageContentType.equals(that.getImageContentType())); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (subject == null) ? 0 : this.subject.hashCode(); - h *= 1000003; - h ^= (imageUri == null) ? 0 : this.imageUri.hashCode(); - h *= 1000003; - h ^= (imageContentType == null) ? 0 : this.imageContentType.hashCode(); - return h; - } - - static final class Builder extends OutgoingCallComposerData.Builder { - private String subject; - private Uri imageUri; - private String imageContentType; - Builder() { - } - private Builder(OutgoingCallComposerData source) { - this.subject = source.getSubject(); - this.imageUri = source.getImageUri(); - this.imageContentType = source.getImageContentType(); - } - @Override - public OutgoingCallComposerData.Builder setSubject(@Nullable String subject) { - this.subject = subject; - return this; - } - @Override - OutgoingCallComposerData.Builder setImageUri(@Nullable Uri imageUri) { - this.imageUri = imageUri; - return this; - } - @Override - OutgoingCallComposerData.Builder setImageContentType(@Nullable String imageContentType) { - this.imageContentType = imageContentType; - return this; - } - @Override - OutgoingCallComposerData autoBuild() { - return new AutoValue_OutgoingCallComposerData( - this.subject, - this.imageUri, - this.imageContentType); - } - } - -} diff --git a/java/com/android/dialer/enrichedcall/EnrichedCallCapabilities.java b/java/com/android/dialer/enrichedcall/EnrichedCallCapabilities.java index b7d780950..c3c78c9c8 100644 --- a/java/com/android/dialer/enrichedcall/EnrichedCallCapabilities.java +++ b/java/com/android/dialer/enrichedcall/EnrichedCallCapabilities.java @@ -16,21 +16,24 @@ package com.android.dialer.enrichedcall; - +import com.google.auto.value.AutoValue; /** Value type holding enriched call capabilities. */ - +@AutoValue public abstract class EnrichedCallCapabilities { public static final EnrichedCallCapabilities NO_CAPABILITIES = - EnrichedCallCapabilities.create(false, false); + EnrichedCallCapabilities.create(false, false, false); public static EnrichedCallCapabilities create( - boolean supportsCallComposer, boolean supportsPostCall) { - return new AutoValue_EnrichedCallCapabilities(supportsCallComposer, supportsPostCall); + boolean supportsCallComposer, boolean supportsPostCall, boolean supportsVideoCall) { + return new AutoValue_EnrichedCallCapabilities( + supportsCallComposer, supportsPostCall, supportsVideoCall); } public abstract boolean supportsCallComposer(); public abstract boolean supportsPostCall(); + + public abstract boolean supportsVideoShare(); } diff --git a/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java b/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java new file mode 100644 index 000000000..5291e292f --- /dev/null +++ b/java/com/android/dialer/enrichedcall/EnrichedCallComponent.java @@ -0,0 +1,48 @@ +/* + * 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.enrichedcall; + +import android.content.Context; +import android.support.annotation.NonNull; +import dagger.Subcomponent; +import com.android.dialer.enrichedcall.stub.EnrichedCallManagerStub; + +/** Subcomponent that can be used to access the enriched call implementation. */ +public class EnrichedCallComponent { + private static EnrichedCallComponent instance; + private EnrichedCallManager enrichedCallManager; + + @NonNull + public EnrichedCallManager getEnrichedCallManager() { + if (enrichedCallManager == null) { + enrichedCallManager = new EnrichedCallManagerStub(); + } + return enrichedCallManager; + } + + public static EnrichedCallComponent get(Context context) { + if (instance == null) { + instance = new EnrichedCallComponent(); + } + return instance; + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + EnrichedCallComponent enrichedCallComponent(); + } +} diff --git a/java/com/android/dialer/enrichedcall/EnrichedCallManager.java b/java/com/android/dialer/enrichedcall/EnrichedCallManager.java index 6af8c409a..a36b2cc0d 100644 --- a/java/com/android/dialer/enrichedcall/EnrichedCallManager.java +++ b/java/com/android/dialer/enrichedcall/EnrichedCallManager.java @@ -16,38 +16,25 @@ package com.android.dialer.enrichedcall; -import android.app.Application; import android.support.annotation.IntDef; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.android.dialer.common.Assert; +import android.support.annotation.WorkerThread; +import com.android.dialer.calldetails.nano.CallDetailsEntries; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult; +import com.android.dialer.enrichedcall.videoshare.VideoShareListener; import com.android.dialer.multimedia.MultimediaData; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Map; /** Performs all enriched calling logic. */ public interface EnrichedCallManager { - /** Factory for {@link EnrichedCallManager}. */ - interface Factory { - EnrichedCallManager getEnrichedCallManager(); - } - - /** Accessor for {@link EnrichedCallManager}. */ - class Accessor { - - /** - * @throws IllegalArgumentException if application does not implement {@link - * EnrichedCallManager.Factory} - */ - @NonNull - public static EnrichedCallManager getInstance(@NonNull Application application) { - Assert.isNotNull(application); - - return ((EnrichedCallManager.Factory) application).getEnrichedCallManager(); - } - } + int POST_CALL_NOTE_MAX_CHAR = 60; /** Receives updates when enriched call capabilities are ready. */ interface CapabilitiesListener { @@ -147,6 +134,15 @@ public interface EnrichedCallManager { @MainThread void endCallComposerSession(long sessionId); + /** + * Sends a post call note to the given number. + * + * @throws IllegalArgumentException if message is longer than {@link #POST_CALL_NOTE_MAX_CHAR} + * characters + */ + @MainThread + void sendPostCallNote(@NonNull String number, @NonNull String message); + /** * Called once the capabilities are available for a corresponding call to {@link * #requestCapabilities(String)}. @@ -162,8 +158,8 @@ public interface EnrichedCallManager { interface StateChangedListener { /** - * Callback fired when state changes. Listeners should call {@link #getSession(String)} to - * retrieve the new state. + * Callback fired when state changes. Listeners should call {@link #getSession(long)} or {@link + * #getSession(String, String)} to retrieve the new state. */ void onEnrichedCallStateChanged(); } @@ -177,16 +173,28 @@ public interface EnrichedCallManager { @MainThread void registerStateChangedListener(@NonNull StateChangedListener listener); - /** Returns the {@link Session} for the given number, or {@code null} if no session exists. */ + /** Returns the {@link Session} for the given unique call id, falling back to the number. */ @MainThread @Nullable - Session getSession(@NonNull String number); + Session getSession(@NonNull String uniqueCallId, @NonNull String number); /** Returns the {@link Session} for the given sessionId, or {@code null} if no session exists. */ @MainThread @Nullable Session getSession(long sessionId); + /** + * Returns a mapping of enriched call data for all of the given {@link CallDetailsEntries}. + * + *

The mapping is created by finding the HistoryResults whose timestamps occurred during or + * close after a CallDetailsEntry. A CallDetailsEntry can have multiple HistoryResults in the + * event that both a CallComposer message and PostCall message were sent for the same call. + */ + @WorkerThread + @NonNull + Map> getAllHistoricalData( + @NonNull String number, @NonNull CallDetailsEntries entries); + /** * Unregisters the given {@link StateChangedListener}. * @@ -222,4 +230,77 @@ public interface EnrichedCallManager { */ @MainThread void onIncomingCallComposerData(long sessionId, @NonNull MultimediaData multimediaData); + + /** + * Called when post call data arrives for the given session. + * + * @throws IllegalStateException if there's no session for the given id + */ + @MainThread + void onIncomingPostCallData(long sessionId, @NonNull MultimediaData multimediaData); + + /** + * Registers the given {@link VideoShareListener}. + * + *

As a result of this method, the listener will receive updates when any video share state + * changes. + */ + @MainThread + void registerVideoShareListener(@NonNull VideoShareListener listener); + + /** + * Unregisters the given {@link VideoShareListener}. + * + *

As a result of this method, the listener will not receive updates when any video share state + * changes. + */ + @MainThread + void unregisterVideoShareListener(@NonNull VideoShareListener listener); + + /** Called when an incoming video share invite is received. */ + @MainThread + void onIncomingVideoShareInvite(long sessionId, @NonNull String number); + + /** + * Starts a video share session with the given remote number. + * + * @param number the remote number in any format + * @return the id for the started session, or {@link Session#NO_SESSION_ID} if the session fails + */ + @MainThread + long startVideoShareSession(@NonNull String number); + + /** + * Accepts a video share session invite. + * + * @param sessionId the session to accept + * @return whether or not accepting the session succeeded + */ + @MainThread + boolean acceptVideoShareSession(long sessionId); + + /** + * Retrieve the session id for an incoming video share invite. + * + * @param number the remote number in any format + * @return the id for the session invite, or {@link Session#NO_SESSION_ID} if there is no invite + */ + @MainThread + long getVideoShareInviteSessionId(@NonNull String number); + + /** + * Ends the given video share session. + * + * @param sessionId the id of the session to end + */ + @MainThread + void endVideoShareSession(long sessionId); + + /** + * Returns the {@link VideoShareSession} for the given sessionId, or {@code null} if no session + * exists. + */ + @MainThread + @Nullable + VideoShareSession getVideoShareSession(long sessionId); } diff --git a/java/com/android/dialer/enrichedcall/EnrichedCallManagerStub.java b/java/com/android/dialer/enrichedcall/EnrichedCallManagerStub.java deleted file mode 100644 index db9a799d3..000000000 --- a/java/com/android/dialer/enrichedcall/EnrichedCallManagerStub.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2016 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.enrichedcall; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import com.android.dialer.multimedia.MultimediaData; - -/** Stub implementation of {@link EnrichedCallManager}. */ -public final class EnrichedCallManagerStub implements EnrichedCallManager { - - @Override - public void registerCapabilitiesListener(@NonNull CapabilitiesListener listener) {} - - @Override - public void requestCapabilities(@NonNull String number) {} - - @Override - public void unregisterCapabilitiesListener(@NonNull CapabilitiesListener listener) {} - - @Override - public EnrichedCallCapabilities getCapabilities(@NonNull String number) { - return null; - } - - @Override - public void clearCachedData() {} - - @Override - public long startCallComposerSession(@NonNull String number) { - return Session.NO_SESSION_ID; - } - - @Override - public void sendCallComposerData(long sessionId, @NonNull MultimediaData data) {} - - @Override - public void endCallComposerSession(long sessionId) {} - - @Override - public void onCapabilitiesReceived( - @NonNull String number, @NonNull EnrichedCallCapabilities capabilities) {} - - @Override - public void registerStateChangedListener(@NonNull StateChangedListener listener) {} - - @Nullable - @Override - public Session getSession(@NonNull String number) { - return null; - } - - @Nullable - @Override - public Session getSession(long sessionId) { - return null; - } - - @Override - public void unregisterStateChangedListener(@NonNull StateChangedListener listener) {} - - @Override - public void onSessionStatusUpdate(long sessionId, @NonNull String number, int state) {} - - @Override - public void onMessageUpdate(long sessionId, @NonNull String messageId, int state) {} - - @Override - public void onIncomingCallComposerData(long sessionId, @NonNull MultimediaData multimediaData) {} -} diff --git a/java/com/android/dialer/enrichedcall/FuzzyPhoneNumberMatcher.java b/java/com/android/dialer/enrichedcall/FuzzyPhoneNumberMatcher.java new file mode 100644 index 000000000..f589f94a6 --- /dev/null +++ b/java/com/android/dialer/enrichedcall/FuzzyPhoneNumberMatcher.java @@ -0,0 +1,20 @@ +package com.android.dialer.enrichedcall; + +import android.support.annotation.NonNull; +import com.android.dialer.common.Assert; + +/** Utility for comparing phone numbers. */ +public class FuzzyPhoneNumberMatcher { + + /** Returns {@code true} if the given numbers can be interpreted to be the same. */ + public static boolean matches(@NonNull String a, @NonNull String b) { + String aNormalized = Assert.isNotNull(a).replaceAll("[^0-9]", ""); + String bNormalized = Assert.isNotNull(b).replaceAll("[^0-9]", ""); + if (aNormalized.length() < 7 || bNormalized.length() < 7) { + return false; + } + String aMatchable = aNormalized.substring(aNormalized.length() - 7); + String bMatchable = bNormalized.substring(bNormalized.length() - 7); + return aMatchable.equals(bMatchable); + } +} diff --git a/java/com/android/dialer/enrichedcall/OutgoingCallComposerData.java b/java/com/android/dialer/enrichedcall/OutgoingCallComposerData.java index a8ee49d4e..56145ddd4 100644 --- a/java/com/android/dialer/enrichedcall/OutgoingCallComposerData.java +++ b/java/com/android/dialer/enrichedcall/OutgoingCallComposerData.java @@ -20,7 +20,7 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.dialer.common.Assert; - +import com.google.auto.value.AutoValue; /** * Value type holding references to all data that could be provided for the call composer. @@ -29,19 +29,19 @@ import com.android.dialer.common.Assert; * *

  *   OutgoingCallComposerData.builder.build(); // throws exception, no data set
- *   OutgoingCallComposerData
- *       .setSubject(subject)
+ *   OutgoingCallComposerData.builder
+ *       .setText(subject)
  *       .build(); // Success
- *   OutgoingCallComposerData
+ *   OutgoingCallComposerData.builder
  *       .setImageData(uri, contentType)
  *       .build(); // Success
- *   OutgoingCallComposerData
- *      .setSubject(subject)
+ *   OutgoingCallComposerData.builder
+ *      .setText(subject)
  *      .setImageData(uri, contentType)
  *      .build(); // Success
  * 
*/ - +@AutoValue public abstract class OutgoingCallComposerData { public static Builder builder() { @@ -62,7 +62,7 @@ public abstract class OutgoingCallComposerData { public abstract String getImageContentType(); /** Builds instances of {@link OutgoingCallComposerData}. */ - + @AutoValue.Builder public abstract static class Builder { public abstract Builder setSubject(String subject); diff --git a/java/com/android/dialer/enrichedcall/Session.java b/java/com/android/dialer/enrichedcall/Session.java index b0439fae9..b3f291438 100644 --- a/java/com/android/dialer/enrichedcall/Session.java +++ b/java/com/android/dialer/enrichedcall/Session.java @@ -17,6 +17,7 @@ package com.android.dialer.enrichedcall; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.android.dialer.enrichedcall.EnrichedCallManager.State; import com.android.dialer.multimedia.MultimediaData; @@ -38,6 +39,12 @@ public interface Session { */ long getSessionId(); + /** Returns the id of the dialer call associated with this session, or null if there isn't one. */ + @Nullable + String getUniqueDialerCallId(); + + void setUniqueDialerCallId(@NonNull String id); + /** Returns the number associated with the remote end of this session. */ @NonNull String getRemoteNumber(); diff --git a/java/com/android/dialer/enrichedcall/StubEnrichedCallModule.java b/java/com/android/dialer/enrichedcall/StubEnrichedCallModule.java deleted file mode 100644 index 39c55d040..000000000 --- a/java/com/android/dialer/enrichedcall/StubEnrichedCallModule.java +++ /dev/null @@ -1,32 +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.enrichedcall; - -import dagger.Module; -import dagger.Provides; -import javax.inject.Singleton; - -/** Module which binds {@link EnrichedCallManagerStub}. */ -@Module -public class StubEnrichedCallModule { - - @Provides - @Singleton - static EnrichedCallManager provideEnrichedCallManager() { - return new EnrichedCallManagerStub(); - } -} diff --git a/java/com/android/dialer/enrichedcall/VideoShareSession.java b/java/com/android/dialer/enrichedcall/VideoShareSession.java new file mode 100644 index 000000000..07bc4ed09 --- /dev/null +++ b/java/com/android/dialer/enrichedcall/VideoShareSession.java @@ -0,0 +1,20 @@ +/* + * 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.enrichedcall; + +/** Holds state information and data about video share sessions. */ +public interface VideoShareSession {} diff --git a/java/com/android/dialer/enrichedcall/historyquery/HistoryQuery.java b/java/com/android/dialer/enrichedcall/historyquery/HistoryQuery.java new file mode 100644 index 000000000..b7593cebb --- /dev/null +++ b/java/com/android/dialer/enrichedcall/historyquery/HistoryQuery.java @@ -0,0 +1,31 @@ +package com.android.dialer.enrichedcall.historyquery; + +import android.support.annotation.NonNull; +import com.android.dialer.common.LogUtil; +import com.google.auto.value.AutoValue; + +/** + * Data object representing the pieces of information required to query for historical enriched call + * data. + */ +@AutoValue +public abstract class HistoryQuery { + + @NonNull + public static HistoryQuery create(@NonNull String number, long callStartTime, long callEndTime) { + return new AutoValue_HistoryQuery(number, callStartTime, callEndTime); + } + + public abstract String getNumber(); + + public abstract long getCallStartTimestamp(); + + public abstract long getCallEndTimestamp(); + + @Override + public String toString() { + return String.format( + "HistoryQuery{number: %s, callStartTimestamp: %d, callEndTimestamp: %d}", + LogUtil.sanitizePhoneNumber(getNumber()), getCallStartTimestamp(), getCallEndTimestamp()); + } +} diff --git a/java/com/android/dialer/enrichedcall/historyquery/nano/HistoryResult.java b/java/com/android/dialer/enrichedcall/historyquery/nano/HistoryResult.java new file mode 100644 index 000000000..2fdc2da50 --- /dev/null +++ b/java/com/android/dialer/enrichedcall/historyquery/nano/HistoryResult.java @@ -0,0 +1,203 @@ +/* + * 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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! + +package com.android.dialer.enrichedcall.historyquery.proto.nano; + +/** This file is autogenerated, but javadoc required. */ +@SuppressWarnings("hiding") +public final class HistoryResult + extends com.google.protobuf.nano.ExtendableMessageNano { + + /** This file is autogenerated, but javadoc required. */ + // enum Type + public interface Type { + public static final int INCOMING_CALL_COMPOSER = 1; + public static final int OUTGOING_CALL_COMPOSER = 2; + public static final int INCOMING_POST_CALL = 3; + public static final int OUTGOING_POST_CALL = 4; + } + + private static volatile HistoryResult[] _emptyArray; + + public static HistoryResult[] emptyArray() { + // Lazily initializes the empty array + if (_emptyArray == null) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + if (_emptyArray == null) { + _emptyArray = new HistoryResult[0]; + } + } + } + return _emptyArray; + } + + // optional .com.android.dialer.enrichedcall.historyquery.proto.HistoryResult.Type type = 1; + public int type; + + // optional string text = 2; + public java.lang.String text; + + // optional string image_uri = 4; + public java.lang.String imageUri; + + // optional string image_content_type = 5; + public java.lang.String imageContentType; + + // optional int64 timestamp = 7; + public long timestamp; + + // @@protoc_insertion_point(class_scope:com.android.dialer.enrichedcall.historyquery.proto.HistoryResult) + + public HistoryResult() { + clear(); + } + + public HistoryResult clear() { + type = + com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .INCOMING_CALL_COMPOSER; + text = ""; + imageUri = ""; + imageContentType = ""; + timestamp = 0L; + unknownFieldData = null; + cachedSize = -1; + return this; + } + + @Override + public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output) + throws java.io.IOException { + if (this.type + != com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .INCOMING_CALL_COMPOSER) { + output.writeInt32(1, this.type); + } + if (this.text != null && !this.text.equals("")) { + output.writeString(2, this.text); + } + if (this.imageUri != null && !this.imageUri.equals("")) { + output.writeString(4, this.imageUri); + } + if (this.imageContentType != null && !this.imageContentType.equals("")) { + output.writeString(5, this.imageContentType); + } + if (this.timestamp != 0L) { + output.writeInt64(7, this.timestamp); + } + super.writeTo(output); + } + + @Override + protected int computeSerializedSize() { + int size = super.computeSerializedSize(); + if (this.type + != com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .INCOMING_CALL_COMPOSER) { + size += com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt32Size(1, this.type); + } + if (this.text != null && !this.text.equals("")) { + size += com.google.protobuf.nano.CodedOutputByteBufferNano.computeStringSize(2, this.text); + } + if (this.imageUri != null && !this.imageUri.equals("")) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeStringSize(4, this.imageUri); + } + if (this.imageContentType != null && !this.imageContentType.equals("")) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeStringSize( + 5, this.imageContentType); + } + if (this.timestamp != 0L) { + size += + com.google.protobuf.nano.CodedOutputByteBufferNano.computeInt64Size(7, this.timestamp); + } + return size; + } + + @Override + public HistoryResult mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) + throws java.io.IOException { + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; + } + case 8: + { + int initialPos = input.getPosition(); + int value = input.readInt32(); + switch (value) { + case com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .INCOMING_CALL_COMPOSER: + case com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .OUTGOING_CALL_COMPOSER: + case com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .INCOMING_POST_CALL: + case com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult.Type + .OUTGOING_POST_CALL: + this.type = value; + break; + default: + input.rewindToPosition(initialPos); + storeUnknownField(input, tag); + break; + } + break; + } + case 18: + { + this.text = input.readString(); + break; + } + case 34: + { + this.imageUri = input.readString(); + break; + } + case 42: + { + this.imageContentType = input.readString(); + break; + } + case 56: + { + this.timestamp = input.readInt64(); + break; + } + } + } + } + + public static HistoryResult parseFrom(byte[] data) + throws com.google.protobuf.nano.InvalidProtocolBufferNanoException { + return com.google.protobuf.nano.MessageNano.mergeFrom(new HistoryResult(), data); + } + + public static HistoryResult parseFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) + throws java.io.IOException { + return new HistoryResult().mergeFrom(input); + } +} diff --git a/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java b/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java new file mode 100644 index 000000000..01d1f2aac --- /dev/null +++ b/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2016 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.enrichedcall.stub; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import android.util.ArrayMap; +import com.android.dialer.calldetails.nano.CallDetailsEntries; +import com.android.dialer.calldetails.nano.CallDetailsEntries.CallDetailsEntry; +import com.android.dialer.common.Assert; +import com.android.dialer.enrichedcall.EnrichedCallCapabilities; +import com.android.dialer.enrichedcall.EnrichedCallManager; +import com.android.dialer.enrichedcall.Session; +import com.android.dialer.enrichedcall.VideoShareSession; +import com.android.dialer.enrichedcall.historyquery.proto.nano.HistoryResult; +import com.android.dialer.enrichedcall.videoshare.VideoShareListener; +import com.android.dialer.multimedia.MultimediaData; +import java.util.List; +import java.util.Map; + +/** Stub implementation of {@link EnrichedCallManager}. */ +public final class EnrichedCallManagerStub implements EnrichedCallManager { + + @Override + public void registerCapabilitiesListener(@NonNull CapabilitiesListener listener) {} + + @Override + public void requestCapabilities(@NonNull String number) {} + + @Override + public void unregisterCapabilitiesListener(@NonNull CapabilitiesListener listener) {} + + @Override + public EnrichedCallCapabilities getCapabilities(@NonNull String number) { + return null; + } + + @Override + public void clearCachedData() {} + + @Override + public long startCallComposerSession(@NonNull String number) { + return Session.NO_SESSION_ID; + } + + @Override + public void sendCallComposerData(long sessionId, @NonNull MultimediaData data) {} + + @Override + public void endCallComposerSession(long sessionId) {} + + @Override + public void sendPostCallNote(@NonNull String number, @NonNull String message) {} + + @Override + public void onCapabilitiesReceived( + @NonNull String number, @NonNull EnrichedCallCapabilities capabilities) {} + + @Override + public void registerStateChangedListener(@NonNull StateChangedListener listener) {} + + @Nullable + @Override + public Session getSession(@NonNull String uniqueCallId, @NonNull String number) { + return null; + } + + @Nullable + @Override + public Session getSession(long sessionId) { + return null; + } + + @NonNull + @Override + @WorkerThread + public Map> getAllHistoricalData( + @NonNull String number, @NonNull CallDetailsEntries entries) { + Assert.isWorkerThread(); + return new ArrayMap<>(); + } + + @Override + public void unregisterStateChangedListener(@NonNull StateChangedListener listener) {} + + @Override + public void onSessionStatusUpdate(long sessionId, @NonNull String number, int state) {} + + @Override + public void onMessageUpdate(long sessionId, @NonNull String messageId, int state) {} + + @Override + public void onIncomingCallComposerData(long sessionId, @NonNull MultimediaData multimediaData) {} + + @Override + public void onIncomingPostCallData(long sessionId, @NonNull MultimediaData multimediaData) {} + + @Override + public void registerVideoShareListener(@NonNull VideoShareListener listener) {} + + @Override + public void unregisterVideoShareListener(@NonNull VideoShareListener listener) {} + + @Override + public void onIncomingVideoShareInvite(long sessionId, @NonNull String number) {} + + @Override + public long startVideoShareSession(String number) { + return Session.NO_SESSION_ID; + } + + @Override + public boolean acceptVideoShareSession(long sessionId) { + return false; + } + + @Override + public long getVideoShareInviteSessionId(@NonNull String number) { + return Session.NO_SESSION_ID; + } + + @Override + public void endVideoShareSession(long sessionId) {} + + @Nullable + @Override + public VideoShareSession getVideoShareSession(long sessionId) { + return null; + } +} diff --git a/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java b/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java new file mode 100644 index 000000000..0ec72111e --- /dev/null +++ b/java/com/android/dialer/enrichedcall/stub/StubEnrichedCallModule.java @@ -0,0 +1,35 @@ +/* + * 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.enrichedcall.stub; + +import com.android.dialer.enrichedcall.EnrichedCallManager; +import dagger.Module; +import dagger.Provides; +import javax.inject.Singleton; + +/** Module which binds {@link EnrichedCallManagerStub}. */ +@Module +public class StubEnrichedCallModule { + + @Provides + @Singleton + static EnrichedCallManager provideEnrichedCallManager() { + return new EnrichedCallManagerStub(); + } + + private StubEnrichedCallModule() {} +} diff --git a/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java b/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java new file mode 100644 index 000000000..bcc387a3f --- /dev/null +++ b/java/com/android/dialer/enrichedcall/videoshare/VideoShareListener.java @@ -0,0 +1,14 @@ +package com.android.dialer.enrichedcall.videoshare; + +import android.support.annotation.MainThread; + +/** Receives updates when video share status has changed. */ +public interface VideoShareListener { + + /** + * Callback fired when video share has changed (service connected / disconnected, video share + * invite received or canceled, or when a session changes). + */ + @MainThread + void onVideoShareChanged(); +} diff --git a/java/com/android/dialer/inject/ApplicationModule.java b/java/com/android/dialer/inject/ApplicationModule.java deleted file mode 100644 index 99e5296ea..000000000 --- a/java/com/android/dialer/inject/ApplicationModule.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2016 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.inject; - -import android.app.Application; -import android.support.annotation.NonNull; -import com.android.dialer.common.Assert; -import dagger.Module; -import dagger.Provides; - -/** Provides the singleton application object. */ -@Module -public final class ApplicationModule { - - @NonNull private final Application application; - - public ApplicationModule(@NonNull Application application) { - this.application = Assert.isNotNull(application); - } - - @Provides - Application provideApplication() { - return application; - } -} diff --git a/java/com/android/dialer/inject/ContextModule.java b/java/com/android/dialer/inject/ContextModule.java new file mode 100644 index 000000000..aa83f0105 --- /dev/null +++ b/java/com/android/dialer/inject/ContextModule.java @@ -0,0 +1,39 @@ +/* + * 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.inject; + +import android.content.Context; +import android.support.annotation.NonNull; +import com.android.dialer.common.Assert; +import dagger.Module; +import dagger.Provides; + +/** Provides the singleton context object. */ +@Module +public final class ContextModule { + + @NonNull private final Context context; + + public ContextModule(@NonNull Context context) { + this.context = Assert.isNotNull(context); + } + + @Provides + Context provideContext() { + return context; + } +} diff --git a/java/com/android/dialer/inject/DialerAppComponent.java b/java/com/android/dialer/inject/DialerAppComponent.java deleted file mode 100644 index 9832ce804..000000000 --- a/java/com/android/dialer/inject/DialerAppComponent.java +++ /dev/null @@ -1,29 +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.inject; - -import com.android.dialer.enrichedcall.EnrichedCallManager; -import com.android.dialer.enrichedcall.StubEnrichedCallModule; -import dagger.Component; -import javax.inject.Singleton; - -/** Core application-wide {@link Component} for the open source dialer app. */ -@Singleton -@Component(modules = {ApplicationModule.class, StubEnrichedCallModule.class}) -public interface DialerAppComponent { - EnrichedCallManager enrichedCallManager(); -} diff --git a/java/com/android/dialer/inject/HasRootComponent.java b/java/com/android/dialer/inject/HasRootComponent.java new file mode 100644 index 000000000..0802b806a --- /dev/null +++ b/java/com/android/dialer/inject/HasRootComponent.java @@ -0,0 +1,25 @@ +/* + * 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.inject; + +/** + * Used by packages to access the root component from the Application without creating a dependency + * cycle. + */ +public interface HasRootComponent { + Object component(); +} diff --git a/java/com/android/dialer/interactions/PhoneNumberInteraction.java b/java/com/android/dialer/interactions/PhoneNumberInteraction.java index f36e5319c..b797629dc 100644 --- a/java/com/android/dialer/interactions/PhoneNumberInteraction.java +++ b/java/com/android/dialer/interactions/PhoneNumberInteraction.java @@ -81,8 +81,10 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener { private static final String TAG = PhoneNumberInteraction.class.getSimpleName(); /** The identifier for a permissions request if one is generated. */ public static final int REQUEST_READ_CONTACTS = 1; + public static final int REQUEST_CALL_PHONE = 2; - private static final String[] PHONE_NUMBER_PROJECTION = + @VisibleForTesting + public static final String[] PHONE_NUMBER_PROJECTION = new String[] { Phone._ID, Phone.NUMBER, @@ -191,13 +193,14 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener { * numbers have been queried for. The activity must implement {@link InteractionErrorListener} * and {@link DisambigDialogDismissedListener}. * @param isVideoCall {@code true} if the call is a video call, {@code false} otherwise. + * @return true if the necessary permissions were found to start the interaction, false otherwise */ - public static void startInteractionForPhoneCall( + public static boolean startInteractionForPhoneCall( TransactionSafeActivity activity, Uri uri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) { - new PhoneNumberInteraction( + return new PhoneNumberInteraction( activity, ContactDisplayUtils.INTERACTION_CALL, isVideoCall, callSpecificAppData) .startInteraction(uri); } @@ -211,11 +214,19 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener { * Initiates the interaction to result in either a phone call or sms message for a contact. * * @param uri Contact Uri + * @return true if the necessary permissions were found to start the interaction, false otherwise */ - private void startInteraction(Uri uri) { - // It's possible for a shortcut to have been created, and then Contacts permissions revoked. To - // avoid a crash when the user tries to use such a shortcut, check for this condition and ask - // the user for the permission. + private boolean startInteraction(Uri uri) { + // It's possible for a shortcut to have been created, and then permissions revoked. To avoid a + // crash when the user tries to use such a shortcut, check for this condition and ask the user + // for the permission. + if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.CALL_PHONE) + != PackageManager.PERMISSION_GRANTED) { + LogUtil.i("PhoneNumberInteraction.startInteraction", "No phone permissions"); + ActivityCompat.requestPermissions( + (Activity) mContext, new String[] {Manifest.permission.CALL_PHONE}, REQUEST_CALL_PHONE); + return false; + } if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { LogUtil.i("PhoneNumberInteraction.startInteraction", "No contact permissions"); @@ -223,7 +234,7 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener { (Activity) mContext, new String[] {Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS); - return; + return false; } if (mLoader != null) { @@ -249,6 +260,7 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener { mContext, queryUri, PHONE_NUMBER_PROJECTION, PHONE_NUMBER_SELECTION, null, null); mLoader.registerListener(0, this); mLoader.startLoading(); + return true; } @Override @@ -457,8 +469,8 @@ public class PhoneNumberInteraction implements OnLoadCompleteListener { * will be chosen to make a call or initiate an sms message. * *

It is recommended to use {@link #startInteractionForPhoneCall(TransactionSafeActivity, Uri, - * boolean, int)} instead of directly using this class, as those methods handle one or multiple - * data cases appropriately. + * boolean, CallSpecificAppData)} instead of directly using this class, as those methods handle + * one or multiple data cases appropriately. * *

This fragment may only be attached to activities which implement {@link * DisambigDialogDismissedListener}. diff --git a/java/com/android/dialer/logging/nano/ContactLookupResult.java b/java/com/android/dialer/logging/nano/ContactLookupResult.java index 8960560fb..93f5f0135 100644 --- a/java/com/android/dialer/logging/nano/ContactLookupResult.java +++ b/java/com/android/dialer/logging/nano/ContactLookupResult.java @@ -11,17 +11,19 @@ * 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 + * limitations under the License. */ // Generated by the protocol buffer compiler. DO NOT EDIT! package com.android.dialer.logging.nano; +/** This file is autogenerated, but javadoc required. */ @SuppressWarnings("hiding") -public final class ContactLookupResult extends - com.google.protobuf.nano.ExtendableMessageNano { +public final class ContactLookupResult + extends com.google.protobuf.nano.ExtendableMessageNano { + /** This file is autogenerated, but javadoc required. */ // enum Type public interface Type { public static final int UNKNOWN_LOOKUP_RESULT_TYPE = 0; @@ -34,11 +36,11 @@ public final class ContactLookupResult extends } private static volatile ContactLookupResult[] _emptyArray; + public static ContactLookupResult[] emptyArray() { // Lazily initializes the empty array if (_emptyArray == null) { - synchronized ( - com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { if (_emptyArray == null) { _emptyArray = new ContactLookupResult[0]; } @@ -60,20 +62,20 @@ public final class ContactLookupResult extends } @Override - public ContactLookupResult mergeFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public ContactLookupResult mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { while (true) { int tag = input.readTag(); switch (tag) { case 0: return this; - default: { - if (!super.storeUnknownField(input, tag)) { - return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; } - break; - } } } } @@ -84,8 +86,7 @@ public final class ContactLookupResult extends } public static ContactLookupResult parseFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) - throws java.io.IOException { + com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { return new ContactLookupResult().mergeFrom(input); } } diff --git a/java/com/android/dialer/logging/nano/ContactSource.java b/java/com/android/dialer/logging/nano/ContactSource.java index 35d8b8ca1..dbe40cd53 100644 --- a/java/com/android/dialer/logging/nano/ContactSource.java +++ b/java/com/android/dialer/logging/nano/ContactSource.java @@ -11,17 +11,19 @@ * 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 + * limitations under the License. */ // Generated by the protocol buffer compiler. DO NOT EDIT! package com.android.dialer.logging.nano; +/** This file is autogenerated, but javadoc required. */ @SuppressWarnings("hiding") -public final class ContactSource extends - com.google.protobuf.nano.ExtendableMessageNano { +public final class ContactSource + extends com.google.protobuf.nano.ExtendableMessageNano { + /** This file is autogenerated, but javadoc required. */ // enum Type public interface Type { public static final int UNKNOWN_SOURCE_TYPE = 0; @@ -33,11 +35,11 @@ public final class ContactSource extends } private static volatile ContactSource[] _emptyArray; + public static ContactSource[] emptyArray() { // Lazily initializes the empty array if (_emptyArray == null) { - synchronized ( - com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { if (_emptyArray == null) { _emptyArray = new ContactSource[0]; } @@ -59,20 +61,20 @@ public final class ContactSource extends } @Override - public ContactSource mergeFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public ContactSource mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { while (true) { int tag = input.readTag(); switch (tag) { case 0: return this; - default: { - if (!super.storeUnknownField(input, tag)) { - return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; } - break; - } } } } @@ -82,8 +84,7 @@ public final class ContactSource extends return com.google.protobuf.nano.MessageNano.mergeFrom(new ContactSource(), data); } - public static ContactSource parseFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public static ContactSource parseFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { return new ContactSource().mergeFrom(input); } diff --git a/java/com/android/dialer/logging/nano/DialerImpression.java b/java/com/android/dialer/logging/nano/DialerImpression.java index 6bb56751f..80a006b55 100644 --- a/java/com/android/dialer/logging/nano/DialerImpression.java +++ b/java/com/android/dialer/logging/nano/DialerImpression.java @@ -14,12 +14,16 @@ * limitations under the License. */ +// Generated by the protocol buffer compiler. DO NOT EDIT! + package com.android.dialer.logging.nano; +/** This file is autogenerated, but javadoc required. */ @SuppressWarnings("hiding") -public final class DialerImpression extends - com.google.protobuf.nano.ExtendableMessageNano { +public final class DialerImpression + extends com.google.protobuf.nano.ExtendableMessageNano { + /** This file is autogenerated, but javadoc required. */ // enum Type public interface Type { public static final int UNKNOWN_AOSP_EVENT_TYPE = 1000; @@ -33,7 +37,8 @@ public final class DialerImpression extends public static final int DIALOG_ACTION_CONFIRM_NUMBER_NOT_SPAM = 1008; public static final int REPORT_AS_NOT_SPAM_VIA_UNBLOCK_NUMBER = 1009; public static final int DIALOG_ACTION_CONFIRM_NUMBER_SPAM_INDIRECTLY_VIA_BLOCK_NUMBER = 1010; - public static final int REPORT_CALL_AS_SPAM_VIA_CALL_LOG_BLOCK_REPORT_SPAM_SENT_VIA_BLOCK_NUMBER_DIALOG = 1011; + public static final int + REPORT_CALL_AS_SPAM_VIA_CALL_LOG_BLOCK_REPORT_SPAM_SENT_VIA_BLOCK_NUMBER_DIALOG = 1011; public static final int USER_ACTION_BLOCKED_NUMBER = 1012; public static final int USER_ACTION_UNBLOCKED_NUMBER = 1013; public static final int SPAM_AFTER_CALL_NOTIFICATION_BLOCK_NUMBER = 1014; @@ -41,7 +46,8 @@ public final class DialerImpression extends public static final int SPAM_AFTER_CALL_NOTIFICATION_SHOW_NON_SPAM_DIALOG = 1016; public static final int SPAM_AFTER_CALL_NOTIFICATION_ADD_TO_CONTACTS = 1019; public static final int SPAM_AFTER_CALL_NOTIFICATION_MARKED_NUMBER_AS_SPAM = 1020; - public static final int SPAM_AFTER_CALL_NOTIFICATION_MARKED_NUMBER_AS_NOT_SPAM_AND_BLOCKED = 1021; + public static final int SPAM_AFTER_CALL_NOTIFICATION_MARKED_NUMBER_AS_NOT_SPAM_AND_BLOCKED = + 1021; public static final int SPAM_AFTER_CALL_NOTIFICATION_REPORT_NUMBER_AS_NOT_SPAM = 1022; public static final int SPAM_AFTER_CALL_NOTIFICATION_ON_DISMISS_SPAM_DIALOG = 1024; public static final int SPAM_AFTER_CALL_NOTIFICATION_ON_DISMISS_NON_SPAM_DIALOG = 1025; @@ -89,7 +95,8 @@ public final class DialerImpression extends public static final int STORAGE_PERMISSION_DENIED = 1073; public static final int CAMERA_PERMISSION_DENIED = 1078; public static final int VOICEMAIL_CONFIGURATION_STATE_CORRUPTION_DETECTED_FROM_ACTIVITY = 1079; - public static final int VOICEMAIL_CONFIGURATION_STATE_CORRUPTION_DETECTED_FROM_NOTIFICATION = 1080; + public static final int VOICEMAIL_CONFIGURATION_STATE_CORRUPTION_DETECTED_FROM_NOTIFICATION = + 1080; public static final int BACKUP_ON_BACKUP = 1081; public static final int BACKUP_ON_FULL_BACKUP = 1082; public static final int BACKUP_ON_BACKUP_DISABLED = 1083; @@ -116,15 +123,43 @@ public final class DialerImpression extends public static final int BACKUP_ON_RESTORE_VM_DUPLICATE_NOT_RESTORING = 1104; public static final int CALL_LOG_SHARE_AND_CALL = 1105; public static final int CALL_COMPOSER_ACTIVITY_PLACE_RCS_CALL = 1106; - public static final int CALL_COMPOSER_ACTIVITY_SEND_AND_CALL_PRESSED_WHEN_SESSION_NOT_READY = 1107; + public static final int CALL_COMPOSER_ACTIVITY_SEND_AND_CALL_PRESSED_WHEN_SESSION_NOT_READY = + 1107; + public static final int POST_CALL_PROMPT_USER_TO_SEND_MESSAGE_CLICKED = 1108; + public static final int POST_CALL_PROMPT_USER_TO_SEND_MESSAGE = 1109; + public static final int POST_CALL_PROMPT_USER_TO_VIEW_SENT_MESSAGE = 1110; + public static final int POST_CALL_PROMPT_USER_TO_VIEW_SENT_MESSAGE_CLICKED = 1111; + public static final int IN_CALL_SCREEN_TURN_ON_MUTE = 1112; + public static final int IN_CALL_SCREEN_TURN_OFF_MUTE = 1113; + public static final int IN_CALL_SCREEN_SWAP_CAMERA = 1114; + public static final int IN_CALL_SCREEN_TURN_ON_VIDEO = 1115; + public static final int IN_CALL_SCREEN_TURN_OFF_VIDEO = 1116; + public static final int VIDEO_CALL_WITH_INCOMING_VOICE_CALL = 1117; + public static final int VIDEO_CALL_WITH_INCOMING_VIDEO_CALL = 1118; + public static final int VOICE_CALL_WITH_INCOMING_VOICE_CALL = 1119; + public static final int VOICE_CALL_WITH_INCOMING_VIDEO_CALL = 1120; + public static final int CALL_DETAILS_COPY_NUMBER = 1121; + public static final int CALL_DETAILS_EDIT_BEFORE_CALL = 1122; + public static final int CALL_DETAILS_CALL_BACK = 1123; + public static final int VVM_USER_DISMISSED_VM_ALMOST_FULL_PROMO = 1124; + public static final int VVM_USER_DISMISSED_VM_FULL_PROMO = 1125; + public static final int VVM_USER_ENABLED_ARCHIVE_FROM_VM_ALMOST_FULL_PROMO = 1126; + public static final int VVM_USER_ENABLED_ARCHIVE_FROM_VM_FULL_PROMO = 1127; + public static final int VVM_USER_SHOWN_VM_ALMOST_FULL_PROMO = 1128; + public static final int VVM_USER_SHOWN_VM_FULL_PROMO = 1129; + public static final int VVM_USER_SHOWN_VM_ALMOST_FULL_ERROR_MESSAGE = 1130; + public static final int VVM_USER_SHOWN_VM_FULL_ERROR_MESSAGE = 1131; + public static final int VVM_USER_TURNED_ARCHIVE_ON_FROM_SETTINGS = 1132; + public static final int VVM_USER_TURNED_ARCHIVE_OFF_FROM_SETTINGS = 1133; + public static final int VVM_ARCHIVE_AUTO_DELETED_VM_FROM_SERVER = 1134; + public static final int VVM_ARCHIVE_AUTO_DELETE_TURNED_OFF = 1135; } private static volatile DialerImpression[] _emptyArray; public static DialerImpression[] emptyArray() { // Lazily initializes the empty array if (_emptyArray == null) { - synchronized ( - com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { if (_emptyArray == null) { _emptyArray = new DialerImpression[0]; } @@ -146,20 +181,20 @@ public final class DialerImpression extends } @Override - public DialerImpression mergeFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public DialerImpression mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { while (true) { int tag = input.readTag(); switch (tag) { case 0: return this; - default: { - if (!super.storeUnknownField(input, tag)) { - return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; } - break; - } } } } @@ -169,10 +204,8 @@ public final class DialerImpression extends return com.google.protobuf.nano.MessageNano.mergeFrom(new DialerImpression(), data); } - public static DialerImpression parseFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public static DialerImpression parseFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { return new DialerImpression().mergeFrom(input); } } - diff --git a/java/com/android/dialer/logging/nano/InteractionEvent.java b/java/com/android/dialer/logging/nano/InteractionEvent.java index 8d9430be9..7ca95fa45 100644 --- a/java/com/android/dialer/logging/nano/InteractionEvent.java +++ b/java/com/android/dialer/logging/nano/InteractionEvent.java @@ -23,8 +23,8 @@ package com.android.dialer.logging.nano; public final class InteractionEvent extends com.google.protobuf.nano.ExtendableMessageNano { - // enum Type /** This file is autogenerated, but javadoc required. */ + // enum Type public interface Type { public static final int UNKNOWN = 0; public static final int CALL_BLOCKED = 15; diff --git a/java/com/android/dialer/logging/nano/ReportingLocation.java b/java/com/android/dialer/logging/nano/ReportingLocation.java index 1f05ce414..08ee04e7e 100644 --- a/java/com/android/dialer/logging/nano/ReportingLocation.java +++ b/java/com/android/dialer/logging/nano/ReportingLocation.java @@ -11,17 +11,19 @@ * 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 + * limitations under the License. */ // Generated by the protocol buffer compiler. DO NOT EDIT! package com.android.dialer.logging.nano; +/** This file is autogenerated, but javadoc required. */ @SuppressWarnings("hiding") -public final class ReportingLocation extends - com.google.protobuf.nano.ExtendableMessageNano { +public final class ReportingLocation + extends com.google.protobuf.nano.ExtendableMessageNano { + /** This file is autogenerated, but javadoc required. */ // enum Type public interface Type { public static final int UNKNOWN_REPORTING_LOCATION = 0; @@ -30,11 +32,11 @@ public final class ReportingLocation extends } private static volatile ReportingLocation[] _emptyArray; + public static ReportingLocation[] emptyArray() { // Lazily initializes the empty array if (_emptyArray == null) { - synchronized ( - com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { + synchronized (com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) { if (_emptyArray == null) { _emptyArray = new ReportingLocation[0]; } @@ -56,20 +58,20 @@ public final class ReportingLocation extends } @Override - public ReportingLocation mergeFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public ReportingLocation mergeFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { while (true) { int tag = input.readTag(); switch (tag) { case 0: return this; - default: { - if (!super.storeUnknownField(input, tag)) { - return this; + default: + { + if (!super.storeUnknownField(input, tag)) { + return this; + } + break; } - break; - } } } } @@ -79,8 +81,7 @@ public final class ReportingLocation extends return com.google.protobuf.nano.MessageNano.mergeFrom(new ReportingLocation(), data); } - public static ReportingLocation parseFrom( - com.google.protobuf.nano.CodedInputByteBufferNano input) + public static ReportingLocation parseFrom(com.google.protobuf.nano.CodedInputByteBufferNano input) throws java.io.IOException { return new ReportingLocation().mergeFrom(input); } diff --git a/java/com/android/dialer/logging/nano/ScreenEvent.java b/java/com/android/dialer/logging/nano/ScreenEvent.java index be4e5eb9e..bd5b817e1 100644 --- a/java/com/android/dialer/logging/nano/ScreenEvent.java +++ b/java/com/android/dialer/logging/nano/ScreenEvent.java @@ -22,8 +22,8 @@ package com.android.dialer.logging.nano; @SuppressWarnings("hiding") public final class ScreenEvent extends com.google.protobuf.nano.ExtendableMessageNano { - // enum Type /** This file is autogenerated, but javadoc required. */ + // enum Type public interface Type { public static final int UNKNOWN = 0; public static final int DIALPAD = 1; diff --git a/java/com/android/dialer/multimedia/AutoValue_MultimediaData.java b/java/com/android/dialer/multimedia/AutoValue_MultimediaData.java deleted file mode 100644 index cc6815094..000000000 --- a/java/com/android/dialer/multimedia/AutoValue_MultimediaData.java +++ /dev/null @@ -1,165 +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.multimedia; - -import android.location.Location; -import android.net.Uri; -import android.support.annotation.Nullable; -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_MultimediaData extends MultimediaData { - - private final String subject; - private final Location location; - private final Uri imageUri; - private final String imageContentType; - private final boolean important; - - private AutoValue_MultimediaData( - @Nullable String subject, - @Nullable Location location, - @Nullable Uri imageUri, - @Nullable String imageContentType, - boolean important) { - this.subject = subject; - this.location = location; - this.imageUri = imageUri; - this.imageContentType = imageContentType; - this.important = important; - } - - @Nullable - @Override - public String getSubject() { - return subject; - } - - @Nullable - @Override - public Location getLocation() { - return location; - } - - @Nullable - @Override - public Uri getImageUri() { - return imageUri; - } - - @Nullable - @Override - public String getImageContentType() { - return imageContentType; - } - - @Override - public boolean isImportant() { - return important; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof MultimediaData) { - MultimediaData that = (MultimediaData) o; - return ((this.subject == null) ? (that.getSubject() == null) : this.subject.equals(that.getSubject())) - && ((this.location == null) ? (that.getLocation() == null) : this.location.equals(that.getLocation())) - && ((this.imageUri == null) ? (that.getImageUri() == null) : this.imageUri.equals(that.getImageUri())) - && ((this.imageContentType == null) ? (that.getImageContentType() == null) : this.imageContentType.equals(that.getImageContentType())) - && (this.important == that.isImportant()); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (subject == null) ? 0 : this.subject.hashCode(); - h *= 1000003; - h ^= (location == null) ? 0 : this.location.hashCode(); - h *= 1000003; - h ^= (imageUri == null) ? 0 : this.imageUri.hashCode(); - h *= 1000003; - h ^= (imageContentType == null) ? 0 : this.imageContentType.hashCode(); - h *= 1000003; - h ^= this.important ? 1231 : 1237; - return h; - } - - static final class Builder extends MultimediaData.Builder { - private String subject; - private Location location; - private Uri imageUri; - private String imageContentType; - private Boolean important; - Builder() { - } - private Builder(MultimediaData source) { - this.subject = source.getSubject(); - this.location = source.getLocation(); - this.imageUri = source.getImageUri(); - this.imageContentType = source.getImageContentType(); - this.important = source.isImportant(); - } - @Override - public MultimediaData.Builder setSubject(@Nullable String subject) { - this.subject = subject; - return this; - } - @Override - public MultimediaData.Builder setLocation(@Nullable Location location) { - this.location = location; - return this; - } - @Override - MultimediaData.Builder setImageUri(@Nullable Uri imageUri) { - this.imageUri = imageUri; - return this; - } - @Override - MultimediaData.Builder setImageContentType(@Nullable String imageContentType) { - this.imageContentType = imageContentType; - return this; - } - @Override - public MultimediaData.Builder setImportant(boolean important) { - this.important = important; - return this; - } - @Override - public MultimediaData build() { - String missing = ""; - if (this.important == null) { - missing += " important"; - } - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } - return new AutoValue_MultimediaData( - this.subject, - this.location, - this.imageUri, - this.imageContentType, - this.important); - } - } - -} diff --git a/java/com/android/dialer/multimedia/MultimediaData.java b/java/com/android/dialer/multimedia/MultimediaData.java index ebd41a918..22bb7641c 100644 --- a/java/com/android/dialer/multimedia/MultimediaData.java +++ b/java/com/android/dialer/multimedia/MultimediaData.java @@ -21,10 +21,10 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.dialer.common.LogUtil; +import com.google.auto.value.AutoValue; - -/** Holds the data associated with an enriched call session. */ - +/** Holds data associated with a call. */ +@AutoValue public abstract class MultimediaData { public static final MultimediaData EMPTY = builder().build(); @@ -34,32 +34,33 @@ public abstract class MultimediaData { return new AutoValue_MultimediaData.Builder().setImportant(false); } - /** Returns the call composer subject if set, or null if this isn't a call composer session. */ + /** + * Returns the text part of this data. + * + *

This field is used for both the call composer session and the post call note. + */ @Nullable - public abstract String getSubject(); + public abstract String getText(); - /** Returns the call composer location if set, or null if this isn't a call composer session. */ + /** Returns the location part of this data. */ @Nullable public abstract Location getLocation(); - /** Returns {@code true} if this session contains image data. */ + /** Returns {@code true} if this object contains image data. */ public boolean hasImageData() { // imageUri and content are always either both null or nonnull return getImageUri() != null && getImageContentType() != null; } - /** Returns the call composer photo if set, or null if this isn't a call composer session. */ + /** Returns the image uri part of this object's image. */ @Nullable public abstract Uri getImageUri(); - /** - * Returns the content type of the image, either image/png or image/jpeg, if set, or null if this - * isn't a call composer session. - */ + /** Returns the content type part of this object's image, either image/png or image/jpeg. */ @Nullable public abstract String getImageContentType(); - /** Returns {@code true} if this is a call composer session that's marked as important. */ + /** Returns {@code true} if this data is marked as important. */ public abstract boolean isImportant(); /** Returns the string form of this MultimediaData with no PII. */ @@ -68,7 +69,7 @@ public abstract class MultimediaData { return String.format( "MultimediaData{subject: %s, location: %s, imageUrl: %s, imageContentType: %s, " + "important: %b}", - LogUtil.sanitizePii(getSubject()), + LogUtil.sanitizePii(getText()), LogUtil.sanitizePii(getLocation()), LogUtil.sanitizePii(getImageUri()), getImageContentType(), @@ -76,10 +77,10 @@ public abstract class MultimediaData { } /** Creates instances of {@link MultimediaData}. */ - + @AutoValue.Builder public abstract static class Builder { - public abstract Builder setSubject(@NonNull String subject); + public abstract Builder setText(@NonNull String subject); public abstract Builder setLocation(@NonNull Location location); diff --git a/java/com/android/dialer/notification/AndroidManifest.xml b/java/com/android/dialer/notification/AndroidManifest.xml new file mode 100644 index 000000000..c5484f263 --- /dev/null +++ b/java/com/android/dialer/notification/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/java/com/android/dialer/notification/GroupedNotificationUtil.java b/java/com/android/dialer/notification/GroupedNotificationUtil.java new file mode 100644 index 000000000..63ea51739 --- /dev/null +++ b/java/com/android/dialer/notification/GroupedNotificationUtil.java @@ -0,0 +1,66 @@ +/* + * 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.notification; + +import android.app.NotificationManager; +import android.service.notification.StatusBarNotification; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.util.Objects; + +/** Utilities for dealing with grouped notifications */ +public final class GroupedNotificationUtil { + + /** + * Remove notification(s) that were added as part of a group. Will ensure that if this is the last + * notification in the group the summary will be removed. + * + * @param tag String tag as included in {@link NotificationManager#notify(String, int, + * android.app.Notification)}. If null will remove all notifications under id + * @param id notification id as included with {@link NotificationManager#notify(String, int, + * android.app.Notification)}. + * @param summaryTag String tag of the summary notification + */ + public static void removeNotification( + @NonNull NotificationManager notificationManager, + @Nullable String tag, + int id, + @NonNull String summaryTag) { + if (tag == null) { + // Clear all missed call notifications + for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { + if (notification.getId() == id) { + notificationManager.cancel(notification.getTag(), id); + } + } + } else { + notificationManager.cancel(tag, id); + + // See if other non-summary missed call notifications exist, and if not then clear the summary + boolean clearSummary = true; + for (StatusBarNotification notification : notificationManager.getActiveNotifications()) { + if (notification.getId() == id && !Objects.equals(summaryTag, notification.getTag())) { + clearSummary = false; + break; + } + } + if (clearSummary) { + notificationManager.cancel(summaryTag, id); + } + } + } +} diff --git a/java/com/android/dialer/notification/NotificationChannelManager.java b/java/com/android/dialer/notification/NotificationChannelManager.java new file mode 100644 index 000000000..9ff57321e --- /dev/null +++ b/java/com/android/dialer/notification/NotificationChannelManager.java @@ -0,0 +1,232 @@ +/* + * 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.notification; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.media.AudioAttributes; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringDef; +import android.support.v4.os.BuildCompat; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; +import com.android.contacts.common.compat.TelephonyManagerCompat; +import com.android.dialer.buildtype.BuildType; +import com.android.dialer.common.LogUtil; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Contains info on how to create {@link NotificationChannel NotificationChannels} */ +public class NotificationChannelManager { + + private static NotificationChannelManager instance; + + public static NotificationChannelManager getInstance() { + if (instance == null) { + instance = new NotificationChannelManager(); + } + return instance; + } + + /** + * Set the channel of notification appropriately. Will create the channel if it does not already + * exist. Safe to call pre-O (will no-op). + * + *

phoneAccount should only be null if channelName is {@link Channel#MISC}. + */ + public static void applyChannel( + @NonNull Notification.Builder notification, + @NonNull Context context, + @Channel String channelName, + @Nullable PhoneAccountHandle phoneAccount) { + if (phoneAccount == null) { + if (!Channel.MISC.equals(channelName)) { + IllegalArgumentException exception = + new IllegalArgumentException( + "Phone account handle must not be null unless on Channel.MISC"); + if (BuildType.get() >= BuildType.RELEASE) { + LogUtil.e("NotificationChannelManager.applyChannel", null, exception); + } else { + throw exception; + } + } + } + + if (BuildCompat.isAtLeastO()) { + NotificationChannel channel = + NotificationChannelManager.getInstance().getChannel(context, channelName, phoneAccount); + notification.setChannel(channel.getId()); + } + } + + /** The base Channel IDs for {@link NotificationChannel} */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Channel.INCOMING_CALL, + Channel.ONGOING_CALL, + Channel.MISSED_CALL, + Channel.VOICEMAIL, + Channel.EXTERNAL_CALL, + Channel.MISC + }) + public @interface Channel { + String INCOMING_CALL = "incomingCall"; + String ONGOING_CALL = "ongoingCall"; + String MISSED_CALL = "missedCall"; + String VOICEMAIL = "voicemail"; + String EXTERNAL_CALL = "externalCall"; + String MISC = "miscellaneous"; + } + + private NotificationChannelManager() {} + + private NotificationChannel getChannel( + @NonNull Context context, + @Channel String channelName, + @Nullable PhoneAccountHandle phoneAccount) { + String channelId = channelNameToId(channelName, phoneAccount); + NotificationChannel channel = getNotificationManager(context).getNotificationChannel(channelId); + if (channel == null) { + channel = createChannel(context, channelName, phoneAccount); + } + return channel; + } + + private static String channelNameToId( + @Channel String name, @Nullable PhoneAccountHandle phoneAccountHandle) { + if (phoneAccountHandle == null) { + return name; + } else { + return name + ":" + phoneAccountHandle.getId(); + } + } + + private NotificationChannel createChannel( + Context context, + @Channel String channelName, + @Nullable PhoneAccountHandle phoneAccountHandle) { + String channelId = channelNameToId(channelName, phoneAccountHandle); + + if (phoneAccountHandle != null) { + PhoneAccount account = getTelecomManager(context).getPhoneAccount(phoneAccountHandle); + NotificationChannelGroup group = + new NotificationChannelGroup( + phoneAccountHandle.getId(), + (account == null) ? phoneAccountHandle.getId() : account.getLabel().toString()); + getNotificationManager(context) + .createNotificationChannelGroup(group); // No-op if already exists + } else if (!Channel.MISC.equals(channelName)) { + LogUtil.w( + "NotificationChannelManager.createChannel", + "Null PhoneAccountHandle with channel " + channelName); + } + + Uri silentRingtone = Uri.parse(""); + + CharSequence name; + int importance; + boolean canShowBadge; + boolean lights; + boolean vibration; + Uri sound; + switch (channelName) { + case Channel.INCOMING_CALL: + name = context.getText(R.string.notification_channel_incoming_call); + importance = NotificationManager.IMPORTANCE_MAX; + canShowBadge = false; + lights = true; + vibration = false; + sound = silentRingtone; + break; + case Channel.MISSED_CALL: + name = context.getText(R.string.notification_channel_missed_call); + importance = NotificationManager.IMPORTANCE_DEFAULT; + canShowBadge = true; + lights = true; + vibration = true; + sound = silentRingtone; + break; + case Channel.ONGOING_CALL: + name = context.getText(R.string.notification_channel_ongoing_call); + importance = NotificationManager.IMPORTANCE_DEFAULT; + canShowBadge = false; + lights = false; + vibration = false; + sound = null; + break; + case Channel.VOICEMAIL: + name = context.getText(R.string.notification_channel_voicemail); + importance = NotificationManager.IMPORTANCE_DEFAULT; + canShowBadge = true; + lights = true; + vibration = true; + sound = + TelephonyManagerCompat.getVoicemailRingtoneUri( + getTelephonyManager(context), phoneAccountHandle); + break; + case Channel.EXTERNAL_CALL: + name = context.getText(R.string.notification_channel_external_call); + importance = NotificationManager.IMPORTANCE_HIGH; + canShowBadge = false; + lights = true; + vibration = true; + sound = null; + break; + case Channel.MISC: + name = context.getText(R.string.notification_channel_misc); + importance = NotificationManager.IMPORTANCE_DEFAULT; + canShowBadge = false; + lights = true; + vibration = true; + sound = null; + break; + default: + throw new IllegalArgumentException("Unknown channel: " + channelName); + } + + NotificationChannel channel = new NotificationChannel(channelId, name, importance); + channel.setShowBadge(canShowBadge); + if (sound != null) { + channel.setSound( + sound, + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()); + } + channel.enableLights(lights); + channel.enableVibration(vibration); + getNotificationManager(context).createNotificationChannel(channel); + return channel; + } + + private static NotificationManager getNotificationManager(@NonNull Context context) { + return context.getSystemService(NotificationManager.class); + } + + private static TelephonyManager getTelephonyManager(@NonNull Context context) { + return context.getSystemService(TelephonyManager.class); + } + + private static TelecomManager getTelecomManager(@NonNull Context context) { + return context.getSystemService(TelecomManager.class); + } +} diff --git a/java/com/android/dialer/notification/res/values/ids.xml b/java/com/android/dialer/notification/res/values/ids.xml new file mode 100644 index 000000000..6bdb489a7 --- /dev/null +++ b/java/com/android/dialer/notification/res/values/ids.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/java/com/android/dialer/notification/res/values/strings.xml b/java/com/android/dialer/notification/res/values/strings.xml new file mode 100644 index 000000000..2fc4962c6 --- /dev/null +++ b/java/com/android/dialer/notification/res/values/strings.xml @@ -0,0 +1,25 @@ + + + + + Incoming calls + Ongoing calls + Missed calls + Voicemails + External calls + Miscellaneous + diff --git a/java/com/android/dialer/oem/AndroidManifest.xml b/java/com/android/dialer/oem/AndroidManifest.xml new file mode 100644 index 000000000..e161a6d14 --- /dev/null +++ b/java/com/android/dialer/oem/AndroidManifest.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java b/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java new file mode 100644 index 000000000..18f621e01 --- /dev/null +++ b/java/com/android/dialer/oem/MotorolaHiddenMenuKeySequence.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * This file is derived in part from code issued under the following license. + * + * 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.oem; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import com.android.dialer.common.LogUtil; +import java.util.regex.Pattern; + +/** + * Util class to handle special char sequence and launch corresponding intent based the sequence. + */ +public class MotorolaHiddenMenuKeySequence { + private static final String EXTRA_HIDDEN_MENU_CODE = "HiddenMenuCode"; + private static MotorolaHiddenMenuKeySequence instance = null; + + private static String[] hiddenKeySequenceArray = null; + private static String[] hiddenKeySequenceIntentArray = null; + private static String[] hiddenKeyPatternArray = null; + private static String[] hiddenKeyPatternIntentArray = null; + private static boolean featureHiddenMenuEnabled = false; + + /** + * Handle input char sequence. + * + * @param context context + * @param input input sequence + * @return true if the input matches any pattern + */ + static boolean handleCharSequence(Context context, String input) { + getInstance(context); + if (!featureHiddenMenuEnabled) { + return false; + } + return handleKeySequence(context, input) || handleKeyPattern(context, input); + } + + /** + * Public interface to return the Singleton instance + * + * @param context the Context + * @return the MotorolaHiddenMenuKeySequence singleton instance + */ + private static synchronized MotorolaHiddenMenuKeySequence getInstance(Context context) { + if (null == instance) { + instance = new MotorolaHiddenMenuKeySequence(context); + } + return instance; + } + + private MotorolaHiddenMenuKeySequence(Context context) { + featureHiddenMenuEnabled = + context.getResources().getBoolean(R.bool.motorola_feature_hidden_menu); + // In case we do have a SPN from resource we need to match from service; otherwise we are + // free to go + if (featureHiddenMenuEnabled) { + + hiddenKeySequenceArray = + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence); + hiddenKeySequenceIntentArray = + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_sequence_intents); + hiddenKeyPatternArray = + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern); + hiddenKeyPatternIntentArray = + context.getResources().getStringArray(R.array.motorola_hidden_menu_key_pattern_intents); + + if (hiddenKeySequenceArray.length != hiddenKeySequenceIntentArray.length + || hiddenKeyPatternArray.length != hiddenKeyPatternIntentArray.length + || (hiddenKeySequenceArray.length == 0 && hiddenKeyPatternArray.length == 0)) { + LogUtil.e( + "MotorolaHiddenMenuKeySequence", + "the key sequence array is not matching, turn off feature." + + "key sequence: %d != %d, key pattern %d != %d", + hiddenKeySequenceArray.length, + hiddenKeySequenceIntentArray.length, + hiddenKeyPatternArray.length, + hiddenKeyPatternIntentArray.length); + featureHiddenMenuEnabled = false; + } + } + } + + private static boolean handleKeyPattern(Context context, String input) { + int len = input.length(); + if (len <= 3 || hiddenKeyPatternArray == null || hiddenKeyPatternIntentArray == null) { + return false; + } + + for (int i = 0; i < hiddenKeyPatternArray.length; i++) { + if ((Pattern.compile(hiddenKeyPatternArray[i])).matcher(input).matches()) { + return sendIntent(context, input, hiddenKeyPatternIntentArray[i]); + } + } + return false; + } + + private static boolean handleKeySequence(Context context, String input) { + int len = input.length(); + if (len <= 3 || hiddenKeySequenceArray == null || hiddenKeySequenceIntentArray == null) { + return false; + } + + for (int i = 0; i < hiddenKeySequenceArray.length; i++) { + if (hiddenKeySequenceArray[i].equals(input)) { + return sendIntent(context, input, hiddenKeySequenceIntentArray[i]); + } + } + return false; + } + + private static boolean sendIntent( + final Context context, final String input, final String action) { + LogUtil.d("MotorolaHiddenMenuKeySequence.sendIntent", "input: %s", input); + try { + Intent intent = new Intent(action); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_HIDDEN_MENU_CODE, input); + + ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0); + + if (resolveInfo != null + && resolveInfo.activityInfo != null + && resolveInfo.activityInfo.enabled) { + context.startActivity(intent); + return true; + } else { + LogUtil.w("MotorolaHiddenMenuKeySequence.sendIntent", "not able to resolve the intent"); + } + } catch (ActivityNotFoundException e) { + LogUtil.e( + "MotorolaHiddenMenuKeySequence.sendIntent", "handleHiddenMenu Key Pattern Exception", e); + } + return false; + } +} diff --git a/java/com/android/dialer/oem/MotorolaUtils.java b/java/com/android/dialer/oem/MotorolaUtils.java new file mode 100644 index 000000000..29bf0b23d --- /dev/null +++ b/java/com/android/dialer/oem/MotorolaUtils.java @@ -0,0 +1,51 @@ +package com.android.dialer.oem; + +import android.content.Context; +import com.android.dialer.common.ConfigProviderBindings; + +/** Util class for Motorola OEM devices. */ +public class MotorolaUtils { + + private static final String CONFIG_HD_CODEC_BLINKING_ICON_WHEN_CONNECTING_CALL_ENABLED = + "hd_codec_blinking_icon_when_connecting_enabled"; + private static final String CONFIG_HD_CODEC_SHOW_ICON_IN_CALL_LOG_ENABLED = + "hd_codec_show_icon_in_call_log_enabled"; + + // This is used to check if a Motorola device supports HD voice call feature, which comes from + // system feature setting. + private static final String HD_CALL_FEATRURE = "com.motorola.software.sprint.hd_call"; + + // Feature flag indicates it's a HD call, currently this is only used by Motorola system build. + // TODO(b/35359461): Upstream and move it to android.provider.CallLog. + private static final int FEATURES_HD_CALL = 0x10000000; + + public static boolean shouldBlinkHdIconWhenConnectingCall(Context context) { + return ConfigProviderBindings.get(context) + .getBoolean(CONFIG_HD_CODEC_BLINKING_ICON_WHEN_CONNECTING_CALL_ENABLED, true) + && isSupportingSprintHdCodec(context); + } + + public static boolean shouldShowHdIconInCallLog(Context context, int features) { + return ConfigProviderBindings.get(context) + .getBoolean(CONFIG_HD_CODEC_SHOW_ICON_IN_CALL_LOG_ENABLED, true) + && isSupportingSprintHdCodec(context) + && (features & FEATURES_HD_CALL) == FEATURES_HD_CALL; + } + + /** + * Handle special char sequence entered in dialpad. This may launch special intent based on input. + * + * @param context context + * @param input input string + * @return true if the input is consumed and the intent is launched + */ + public static boolean handleSpecialCharSequence(Context context, String input) { + // TODO(b/35395377): Add check for Motorola devices. + return MotorolaHiddenMenuKeySequence.handleCharSequence(context, input); + } + + private static boolean isSupportingSprintHdCodec(Context context) { + return context.getPackageManager().hasSystemFeature(HD_CALL_FEATRURE) + && context.getResources().getBoolean(R.bool.motorola_sprint_hd_codec); + } +} diff --git a/java/com/android/dialer/oem/res/values/motorola_config.xml b/java/com/android/dialer/oem/res/values/motorola_config.xml new file mode 100644 index 000000000..f875d573d --- /dev/null +++ b/java/com/android/dialer/oem/res/values/motorola_config.xml @@ -0,0 +1,64 @@ + + + + false + + + + false + + + + ##66236# + ##2539# + ##786# + ##72786# + ##3282# + ##33284# + ##3424# + ##564# + ##4567257# + ##873283# + ##6343# + ##27263# + ##258# + ##8422# + ##4382# + + + com.motorola.intent.action.LAUNCH_HIDDEN_MENU + + + + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + com.motorola.android.intent.action.omadm.sprint.hfa + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + @string/motorola_hidden_menu_intent + + + + + + ##[0-9]{6}# + + + + + @string/motorola_hidden_menu_intent + + \ No newline at end of file diff --git a/java/com/android/dialer/p13n/inference/P13nRanking.java b/java/com/android/dialer/p13n/inference/P13nRanking.java index 6bfc0352a..0682e85db 100644 --- a/java/com/android/dialer/p13n/inference/P13nRanking.java +++ b/java/com/android/dialer/p13n/inference/P13nRanking.java @@ -22,6 +22,7 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.dialer.common.Assert; +import com.android.dialer.common.ConfigProviderBindings; import com.android.dialer.p13n.inference.protocol.P13nRanker; import com.android.dialer.p13n.inference.protocol.P13nRankerFactory; import java.util.List; @@ -38,37 +39,51 @@ public final class P13nRanking { public static P13nRanker get(@NonNull Context context) { Assert.isNotNull(context); Assert.isMainThread(); + if (ranker != null) { return ranker; } + if (!ConfigProviderBindings.get(context).getBoolean("p13n_ranker_should_enable", false)) { + setToIdentityRanker(); + return ranker; + } + Context application = context.getApplicationContext(); if (application instanceof P13nRankerFactory) { ranker = ((P13nRankerFactory) application).newP13nRanker(); } if (ranker == null) { - ranker = - new P13nRanker() { - @Override - public void refresh(@Nullable P13nRefreshCompleteListener listener) {} - - @Override - public List rankList(List phoneNumbers) { - return phoneNumbers; - } - - @NonNull - @Override - public Cursor rankCursor( - @NonNull Cursor phoneQueryResults, int phoneNumberColumnIndex) { - return phoneQueryResults; - } - }; + setToIdentityRanker(); } return ranker; } + private static void setToIdentityRanker() { + ranker = + new P13nRanker() { + @Override + public void refresh(@Nullable P13nRefreshCompleteListener listener) {} + + @Override + public List rankList(List phoneNumbers) { + return phoneNumbers; + } + + @NonNull + @Override + public Cursor rankCursor(@NonNull Cursor phoneQueryResults, int queryLength) { + return phoneQueryResults; + } + + @Override + public boolean shouldShowEmptyListForNullQuery() { + return true; + } + }; + } + public static void setForTesting(@NonNull P13nRanker ranker) { P13nRanking.ranker = ranker; } diff --git a/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java b/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java index 9a859a6db..41f1de49d 100644 --- a/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java +++ b/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java @@ -41,13 +41,15 @@ public interface P13nRanker { * input cursor is closed or invalid, or if any other error occurs in the ranking process. * * @param phoneQueryResults cursor of results of a Dialer search query - * @param phoneNumberColumnIndex column index of the phone number in the cursor data + * @param queryLength length of the search query that resulted in the cursor data, if below 0, + * assumes no length is specified, thus applies the default behavior which is same as when + * queryLength is greater than zero. * @return new cursor of data reordered by ranking (or reference to input cursor if order * unchanged) */ @NonNull @MainThread - Cursor rankCursor(@NonNull Cursor phoneQueryResults, int phoneNumberColumnIndex); + Cursor rankCursor(@NonNull Cursor phoneQueryResults, int queryLength); /** * Refreshes ranking cache (pulls fresh contextual features, pre-caches inference results, etc.). @@ -61,6 +63,10 @@ public interface P13nRanker { @MainThread void refresh(@Nullable P13nRefreshCompleteListener listener); + /** Decides if results should be displayed for no-query search. */ + @MainThread + boolean shouldShowEmptyListForNullQuery(); + /** * Callback class for when ranking refresh has completed. * diff --git a/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java b/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java index 03b77b91c..f443d56fb 100644 --- a/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java +++ b/java/com/android/dialer/phonenumbercache/CachedNumberLookupService.java @@ -18,7 +18,9 @@ package com.android.dialer.phonenumbercache; import android.content.Context; import android.net.Uri; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; import java.io.InputStream; public interface CachedNumberLookupService { @@ -35,6 +37,7 @@ public interface CachedNumberLookupService { * found in the cache, {@link ContactInfo#EMPTY} if the phone number was not found in the * cache, and null if there was an error when querying the cache. */ + @WorkerThread CachedContactInfo lookupCachedContactFromNumber(Context context, String number); void addContact(Context context, CachedContactInfo info); @@ -64,6 +67,7 @@ public interface CachedNumberLookupService { int SOURCE_TYPE_PROFILE = 4; int SOURCE_TYPE_CNAP = 5; + @NonNull ContactInfo getContactInfo(); void setSource(int sourceType, String name, long directoryId); diff --git a/java/com/android/dialer/postcall/AndroidManifest.xml b/java/com/android/dialer/postcall/AndroidManifest.xml new file mode 100644 index 000000000..2bf07bca2 --- /dev/null +++ b/java/com/android/dialer/postcall/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/java/com/android/dialer/postcall/PostCall.java b/java/com/android/dialer/postcall/PostCall.java new file mode 100644 index 000000000..cfe7c867b --- /dev/null +++ b/java/com/android/dialer/postcall/PostCall.java @@ -0,0 +1,182 @@ +/* + * 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.postcall; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.support.design.widget.BaseTransientBottomBar.BaseCallback; +import android.support.design.widget.Snackbar; +import android.view.View; +import android.view.View.OnClickListener; +import com.android.dialer.buildtype.BuildType; +import com.android.dialer.common.Assert; +import com.android.dialer.common.ConfigProvider; +import com.android.dialer.common.ConfigProviderBindings; +import com.android.dialer.common.LogUtil; +import com.android.dialer.logging.Logger; +import com.android.dialer.logging.nano.DialerImpression; +import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.IntentUtil; + +/** Helper class to handle all post call actions. */ +public class PostCall { + + private static final String KEY_POST_CALL_CALL_CONNECT_TIME = "post_call_call_connect_time"; + private static final String KEY_POST_CALL_CALL_DISCONNECT_TIME = "post_call_call_disconnect_time"; + private static final String KEY_POST_CALL_CALL_NUMBER = "post_call_call_number"; + private static final String KEY_POST_CALL_MESSAGE_SENT = "post_call_message_sent"; + + public static void promptUserForMessageIfNecessary(Activity activity, View rootView) { + if (isEnabled(activity)) { + if (shouldPromptUserToViewSentMessage(activity)) { + promptUserToViewSentMessage(activity, rootView); + } else if (shouldPromptUserToSendMessage(activity)) { + promptUserToSendMessage(activity, rootView); + } + } + } + + private static void promptUserToSendMessage(Activity activity, View rootView) { + LogUtil.i("PostCall.promptUserToSendMessage", "returned from call, showing post call SnackBar"); + String message = activity.getString(R.string.post_call_message); + String addMessage = activity.getString(R.string.post_call_add_message); + OnClickListener onClickListener = + v -> { + Logger.get(activity) + .logImpression(DialerImpression.Type.POST_CALL_PROMPT_USER_TO_SEND_MESSAGE_CLICKED); + activity.startActivity(PostCallActivity.newIntent(activity, getPhoneNumber(activity))); + }; + + Snackbar.make(rootView, message, Snackbar.LENGTH_INDEFINITE) + .setAction(addMessage, onClickListener) + .setActionTextColor( + activity.getResources().getColor(R.color.dialer_snackbar_action_text_color)) + .show(); + Logger.get(activity).logImpression(DialerImpression.Type.POST_CALL_PROMPT_USER_TO_SEND_MESSAGE); + PreferenceManager.getDefaultSharedPreferences(activity) + .edit() + .remove(KEY_POST_CALL_CALL_DISCONNECT_TIME) + .apply(); + } + + private static void promptUserToViewSentMessage(Activity activity, View rootView) { + LogUtil.i( + "PostCall.promptUserToViewSentMessage", + "returned from sending a post call message, message sent."); + String message = activity.getString(R.string.post_call_message_sent); + String addMessage = activity.getString(R.string.view); + OnClickListener onClickListener = + v -> { + Logger.get(activity) + .logImpression( + DialerImpression.Type.POST_CALL_PROMPT_USER_TO_VIEW_SENT_MESSAGE_CLICKED); + Intent intent = IntentUtil.getSendSmsIntent(getPhoneNumber(activity)); + DialerUtils.startActivityWithErrorToast(activity, intent); + }; + + Snackbar.make(rootView, message, Snackbar.LENGTH_INDEFINITE) + .setAction(addMessage, onClickListener) + .setActionTextColor( + activity.getResources().getColor(R.color.dialer_snackbar_action_text_color)) + .addCallback( + new BaseCallback() { + @Override + public void onDismissed(Snackbar snackbar, int i) { + super.onDismissed(snackbar, i); + clear(snackbar.getContext()); + } + }) + .show(); + Logger.get(activity) + .logImpression(DialerImpression.Type.POST_CALL_PROMPT_USER_TO_VIEW_SENT_MESSAGE); + PreferenceManager.getDefaultSharedPreferences(activity) + .edit() + .remove(KEY_POST_CALL_MESSAGE_SENT) + .apply(); + } + + public static void onCallDisconnected(Context context, String number, long callConnectedMillis) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putLong(KEY_POST_CALL_CALL_CONNECT_TIME, callConnectedMillis) + .putLong(KEY_POST_CALL_CALL_DISCONNECT_TIME, System.currentTimeMillis()) + .putString(KEY_POST_CALL_CALL_NUMBER, number) + .apply(); + } + + public static void onMessageSent(Context context, String number) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putString(KEY_POST_CALL_CALL_NUMBER, number) + .putBoolean(KEY_POST_CALL_MESSAGE_SENT, true) + .apply(); + } + + private static void clear(Context context) { + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .remove(KEY_POST_CALL_CALL_DISCONNECT_TIME) + .remove(KEY_POST_CALL_CALL_NUMBER) + .remove(KEY_POST_CALL_MESSAGE_SENT) + .remove(KEY_POST_CALL_CALL_CONNECT_TIME) + .apply(); + } + + private static boolean shouldPromptUserToSendMessage(Context context) { + SharedPreferences manager = PreferenceManager.getDefaultSharedPreferences(context); + long disconnectTimeMillis = manager.getLong(KEY_POST_CALL_CALL_DISCONNECT_TIME, -1); + long connectTimeMillis = manager.getLong(KEY_POST_CALL_CALL_CONNECT_TIME, -1); + + long timeSinceDisconnect = System.currentTimeMillis() - disconnectTimeMillis; + long callDurationMillis = disconnectTimeMillis - connectTimeMillis; + + ConfigProvider binding = ConfigProviderBindings.get(context); + return disconnectTimeMillis != -1 + && connectTimeMillis != -1 + && binding.getLong("postcall_last_call_threshold", 30_000) > timeSinceDisconnect + && binding.getLong("postcall_call_duration_threshold", 60_000) > callDurationMillis; + } + + private static boolean shouldPromptUserToViewSentMessage(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(KEY_POST_CALL_MESSAGE_SENT, false); + } + + private static String getPhoneNumber(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(KEY_POST_CALL_CALL_NUMBER, null); + } + + private static boolean isEnabled(Context context) { + @BuildType.Type int type = BuildType.get(); + switch (type) { + case BuildType.BUGFOOD: + case BuildType.DOGFOOD: + case BuildType.FISHFOOD: + case BuildType.TEST: + return ConfigProviderBindings.get(context).getBoolean("enable_post_call", true); + case BuildType.RELEASE: + return ConfigProviderBindings.get(context).getBoolean("enable_post_call_prod", true); + default: + Assert.fail(); + return false; + } + } +} diff --git a/java/com/android/dialer/postcall/PostCallActivity.java b/java/com/android/dialer/postcall/PostCallActivity.java new file mode 100644 index 000000000..8da03dcd1 --- /dev/null +++ b/java/com/android/dialer/postcall/PostCallActivity.java @@ -0,0 +1,151 @@ +/* + * 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.postcall; + +import android.Manifest.permission; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.telephony.SmsManager; +import android.widget.Toolbar; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.enrichedcall.EnrichedCallCapabilities; +import com.android.dialer.enrichedcall.EnrichedCallComponent; +import com.android.dialer.enrichedcall.EnrichedCallManager; +import com.android.dialer.util.PermissionsUtil; +import com.android.dialer.widget.MessageFragment; + +/** Activity used to send post call messages after a phone call. */ +public class PostCallActivity extends AppCompatActivity implements MessageFragment.Listener { + + public static final String KEY_PHONE_NUMBER = "phone_number"; + public static final String KEY_MESSAGE = "message"; + private static final int REQUEST_CODE_SEND_SMS = 1; + + private boolean useRcs; + + public static Intent newIntent(@NonNull Context context, @NonNull String number) { + Intent intent = new Intent(Assert.isNotNull(context), PostCallActivity.class); + intent.putExtra(KEY_PHONE_NUMBER, Assert.isNotNull(number)); + return intent; + } + + @Override + protected void onCreate(@Nullable Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.post_call_activity); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + toolbar.setTitle(getString(R.string.post_call_message)); + toolbar.setNavigationOnClickListener(v -> finish()); + + useRcs = canUseRcs(getIntent().getStringExtra(KEY_PHONE_NUMBER)); + LogUtil.i("PostCallActivity.onCreate", "useRcs: %b", useRcs); + + int postCallCharLimit = + useRcs + ? getResources().getInteger(R.integer.post_call_char_limit) + : MessageFragment.NO_CHAR_LIMIT; + String[] messages = + new String[] { + getString(R.string.post_call_message_1), + getString(R.string.post_call_message_2), + getString(R.string.post_call_message_3) + }; + MessageFragment fragment = + MessageFragment.builder() + .setCharLimit(postCallCharLimit) + .showSendIcon() + .setMessages(messages) + .build(); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.message_container, fragment) + .commit(); + } + + private boolean canUseRcs(@NonNull String number) { + EnrichedCallCapabilities capabilities = + getEnrichedCallManager().getCapabilities(Assert.isNotNull(number)); + LogUtil.i( + "PostCallActivity.canUseRcs", + "number: %s, capabilities: %s", + LogUtil.sanitizePhoneNumber(number), + capabilities); + return capabilities != null && capabilities.supportsPostCall(); + } + + @Override + public void onMessageFragmentSendMessage(@NonNull String message) { + String number = Assert.isNotNull(getIntent().getStringExtra(KEY_PHONE_NUMBER)); + getIntent().putExtra(KEY_MESSAGE, message); + + if (useRcs) { + LogUtil.i("PostCallActivity.onMessageFragmentSendMessage", "sending post call Rcs."); + getEnrichedCallManager().sendPostCallNote(number, message); + PostCall.onMessageSent(this, number); + finish(); + } else if (PermissionsUtil.hasPermission(this, permission.SEND_SMS)) { + LogUtil.i("PostCallActivity.sendMessage", "Sending post call SMS."); + SmsManager smsManager = SmsManager.getDefault(); + smsManager.sendMultipartTextMessage( + number, null, smsManager.divideMessage(message), null, null); + PostCall.onMessageSent(this, number); + finish(); + } else if (PermissionsUtil.isFirstRequest(this, permission.SEND_SMS) + || shouldShowRequestPermissionRationale(permission.SEND_SMS)) { + LogUtil.i("PostCallActivity.sendMessage", "Requesting SMS_SEND permission."); + requestPermissions(new String[] {permission.SEND_SMS}, REQUEST_CODE_SEND_SMS); + } else { + LogUtil.i( + "PostCallActivity.sendMessage", "Permission permanently denied, sending to settings."); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setData(Uri.parse("package:" + this.getPackageName())); + startActivity(intent); + } + } + + @Override + public void onMessageFragmentAfterTextChange(String message) {} + + @Override + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (permissions.length > 0 && permissions[0].equals(permission.SEND_SMS)) { + PermissionsUtil.permissionRequested(this, permissions[0]); + } + if (requestCode == REQUEST_CODE_SEND_SMS + && grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + onMessageFragmentSendMessage(getIntent().getStringExtra(KEY_MESSAGE)); + } + } + + @NonNull + private EnrichedCallManager getEnrichedCallManager() { + return EnrichedCallComponent.get(this).getEnrichedCallManager(); + } +} diff --git a/java/com/android/dialer/postcall/res/layout/post_call_activity.xml b/java/com/android/dialer/postcall/res/layout/post_call_activity.xml new file mode 100644 index 000000000..6ea8126c5 --- /dev/null +++ b/java/com/android/dialer/postcall/res/layout/post_call_activity.xml @@ -0,0 +1,38 @@ + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/postcall/res/values/strings.xml b/java/com/android/dialer/postcall/res/values/strings.xml new file mode 100644 index 000000000..d5e085a05 --- /dev/null +++ b/java/com/android/dialer/postcall/res/values/strings.xml @@ -0,0 +1,31 @@ + + + + + Say why you called + + This is urgent. Call me back. + + Call me back when you have some time. + + Not urgent, we can chat later. + + Add message + + Message sent + View + \ No newline at end of file diff --git a/java/com/android/dialer/postcall/res/values/values.xml b/java/com/android/dialer/postcall/res/values/values.xml new file mode 100644 index 000000000..64fe9f6c8 --- /dev/null +++ b/java/com/android/dialer/postcall/res/values/values.xml @@ -0,0 +1,19 @@ + + + + 60 + \ No newline at end of file diff --git a/java/com/android/dialer/proguard/proguard.flags b/java/com/android/dialer/proguard/proguard.flags new file mode 100644 index 000000000..0f684a0b3 --- /dev/null +++ b/java/com/android/dialer/proguard/proguard.flags @@ -0,0 +1,6 @@ +# Keep the annotation, classes, methods, and fields marked as UsedByReflection +-keep class com.android.dialer.proguard.UsedByReflection +-keep @com.android.dialer.proguard.UsedByReflection class * +-keepclassmembers class * { + @com.android.dialer.proguard.UsedByReflection *; +} diff --git a/java/com/android/dialer/proguard/proguard_base.flags b/java/com/android/dialer/proguard/proguard_base.flags new file mode 100644 index 000000000..7b5794ec7 --- /dev/null +++ b/java/com/android/dialer/proguard/proguard_base.flags @@ -0,0 +1,74 @@ +# Copied from http://google3/java/com/google/android/apps/common/proguard/base.flags + +# This file is intended to contain proguard options that *nobody* would ever +# not want, in *any* configuration - they ensure basic correctness, and have +# no downsides. You probably do not want to make changes to this file. + +# The presence of both of these attributes causes dalvik and other jvms to print +# stack traces on uncaught exceptions, which is necessary to get useful crash +# reports. +-keepattributes SourceFile,LineNumberTable + +# Preverification was introduced in Java 6 to enable faster classloading, but +# dex doesn't use the java .class format, so it has no benefit and can cause +# problems. +-dontpreverify + +# Skipping analysis of some classes may make proguard strip something that's +# needed. +-dontskipnonpubliclibraryclasses + +# Case-insensitive filesystems can't handle when a.class and A.class exist in +# the same directory. +-dontusemixedcaseclassnames + +# This prevents the names of native methods from being obfuscated and prevents +# UnsatisfiedLinkErrors. +-keepclasseswithmembernames class * { + native ; +} + +# hackbod discourages the use of enums on android, but if you use them, they +# should work. Allow instantiation via reflection by keeping the values method. +-keepclassmembers enum * { + public static **[] values(); +} + +# Parcel reflectively accesses this field. +-keepclassmembers class * implements android.os.Parcelable { + public static *** CREATOR; +} + +# These methods are needed to ensure that serialization behaves as expected when +# classes are obfuscated, shrunk, and/or optimized. +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} + +# Don't warn about Guava. Any Guava-using app will fail the proguard stage without this dontwarn, +# and since Guava is so widely used, we include it here in the base. +-dontwarn com.google.common.** + +# Don't warn about Error Prone annotations (e.g. @CompileTimeConstant) +-dontwarn com.google.errorprone.annotations.** + +# Based on http://ag/718466: android.app.Notification.setLatestEventInfo() was +# removed in MNC, but is still referenced (safely) by the NotificationCompat +# code. +-dontwarn android.app.Notification + +# Silence notes about dynamically referenced classes from AOSP support +# libraries. +-dontnote android.graphics.Insets + +# AOSP support library: ICU references to gender and plurals messages. +-dontnote libcore.icu.ICU +-keep class libcore.icu.ICU { *** get(...);} + +# AOSP support library: Handle classes that use reflection. +-dontnote android.support.v4.app.NotificationCompatJellybean diff --git a/java/com/android/dialer/proguard/proguard_release.flags b/java/com/android/dialer/proguard/proguard_release.flags new file mode 100644 index 000000000..1c845cfa3 --- /dev/null +++ b/java/com/android/dialer/proguard/proguard_release.flags @@ -0,0 +1,24 @@ +# Copied from http://google3/java/com/google/android/apps/common/proguard/release.flags + +# Used for building release binaries. Obfuscates, optimizes, and shrinks. + +# By default, proguard leaves all classes in their original package, which +# needlessly repeats com.google.android.apps.etc. +-repackageclasses "" + +# Allows proguard to make private and protected methods and fields public as +# part of optimization. This lets proguard inline trivial getter/setter methods. +-allowaccessmodification + +# The source file attribute must be present in order to print stack traces, but +# we rename it in order to avoid leaking the pre-obfuscation class name. +-renamesourcefileattribute PG + +# This allows proguard to strip isLoggable() blocks containing only debug log +# code from release builds. +-assumenosideeffects class android.util.Log { + static *** i(...); + static *** d(...); + static *** v(...); + static *** isLoggable(...); +} diff --git a/java/com/android/dialer/shortcuts/AutoValue_DialerShortcut.java b/java/com/android/dialer/shortcuts/AutoValue_DialerShortcut.java deleted file mode 100644 index ef995c816..000000000 --- a/java/com/android/dialer/shortcuts/AutoValue_DialerShortcut.java +++ /dev/null @@ -1,161 +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.shortcuts; - -import android.support.annotation.NonNull; -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_DialerShortcut extends DialerShortcut { - - private final long contactId; - private final String lookupKey; - private final String displayName; - private final int rank; - - private AutoValue_DialerShortcut( - long contactId, - String lookupKey, - String displayName, - int rank) { - this.contactId = contactId; - this.lookupKey = lookupKey; - this.displayName = displayName; - this.rank = rank; - } - - @Override - long getContactId() { - return contactId; - } - - @NonNull - @Override - String getLookupKey() { - return lookupKey; - } - - @NonNull - @Override - String getDisplayName() { - return displayName; - } - - @Override - int getRank() { - return rank; - } - - @Override - public String toString() { - return "DialerShortcut{" - + "contactId=" + contactId + ", " - + "lookupKey=" + lookupKey + ", " - + "displayName=" + displayName + ", " - + "rank=" + rank - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof DialerShortcut) { - DialerShortcut that = (DialerShortcut) o; - return (this.contactId == that.getContactId()) - && (this.lookupKey.equals(that.getLookupKey())) - && (this.displayName.equals(that.getDisplayName())) - && (this.rank == that.getRank()); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= (this.contactId >>> 32) ^ this.contactId; - h *= 1000003; - h ^= this.lookupKey.hashCode(); - h *= 1000003; - h ^= this.displayName.hashCode(); - h *= 1000003; - h ^= this.rank; - return h; - } - - static final class Builder extends DialerShortcut.Builder { - private Long contactId; - private String lookupKey; - private String displayName; - private Integer rank; - Builder() { - } - private Builder(DialerShortcut source) { - this.contactId = source.getContactId(); - this.lookupKey = source.getLookupKey(); - this.displayName = source.getDisplayName(); - this.rank = source.getRank(); - } - @Override - DialerShortcut.Builder setContactId(long contactId) { - this.contactId = contactId; - return this; - } - @Override - DialerShortcut.Builder setLookupKey(String lookupKey) { - this.lookupKey = lookupKey; - return this; - } - @Override - DialerShortcut.Builder setDisplayName(String displayName) { - this.displayName = displayName; - return this; - } - @Override - DialerShortcut.Builder setRank(int rank) { - this.rank = rank; - return this; - } - @Override - DialerShortcut build() { - String missing = ""; - if (this.contactId == null) { - missing += " contactId"; - } - if (this.lookupKey == null) { - missing += " lookupKey"; - } - if (this.displayName == null) { - missing += " displayName"; - } - if (this.rank == null) { - missing += " rank"; - } - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } - return new AutoValue_DialerShortcut( - this.contactId, - this.lookupKey, - this.displayName, - this.rank); - } - } - -} \ No newline at end of file diff --git a/java/com/android/dialer/shortcuts/CallContactActivity.java b/java/com/android/dialer/shortcuts/CallContactActivity.java index 1e9a01b39..40bf97b87 100644 --- a/java/com/android/dialer/shortcuts/CallContactActivity.java +++ b/java/com/android/dialer/shortcuts/CallContactActivity.java @@ -56,11 +56,20 @@ public class CallContactActivity extends TransactionSafeActivity } } + /** + * Attempt to make a call, finishing the activity if the required permissions are already granted. + * If the required permissions are not already granted, the activity is not finished so that the + * user can choose to grant or deny them. + */ private void makeCall() { CallSpecificAppData callSpecificAppData = new CallSpecificAppData(); callSpecificAppData.callInitiationType = CallInitiationType.Type.LAUNCHER_SHORTCUT; - PhoneNumberInteraction.startInteractionForPhoneCall( - this, contactUri, false /* isVideoCall */, callSpecificAppData); + boolean interactionStarted = + PhoneNumberInteraction.startInteractionForPhoneCall( + this, contactUri, false /* isVideoCall */, callSpecificAppData); + if (interactionStarted) { + finish(); + } } @Override @@ -115,6 +124,7 @@ public class CallContactActivity extends TransactionSafeActivity int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case PhoneNumberInteraction.REQUEST_READ_CONTACTS: + case PhoneNumberInteraction.REQUEST_CALL_PHONE: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -122,8 +132,8 @@ public class CallContactActivity extends TransactionSafeActivity } else { Toast.makeText(this, R.string.dialer_shortcut_no_permissions, Toast.LENGTH_SHORT) .show(); + finish(); } - finish(); break; } default: diff --git a/java/com/android/dialer/shortcuts/DialerShortcut.java b/java/com/android/dialer/shortcuts/DialerShortcut.java index f2fb3301a..a8d4204fe 100644 --- a/java/com/android/dialer/shortcuts/DialerShortcut.java +++ b/java/com/android/dialer/shortcuts/DialerShortcut.java @@ -22,7 +22,7 @@ import android.net.Uri; import android.os.Build.VERSION_CODES; import android.provider.ContactsContract.Contacts; import android.support.annotation.NonNull; - +import com.google.auto.value.AutoValue; /** * Convenience data structure. @@ -31,7 +31,7 @@ import android.support.annotation.NonNull; * convenience methods for doing things like constructing labels. */ @TargetApi(VERSION_CODES.N_MR1) // Shortcuts introduced in N MR1 - +@AutoValue abstract class DialerShortcut { /** Marker value indicates that shortcut has no setRank. Used by pinned shortcuts. */ @@ -160,7 +160,7 @@ abstract class DialerShortcut { return new AutoValue_DialerShortcut.Builder().setRank(NO_RANK); } - + @AutoValue.Builder abstract static class Builder { /** diff --git a/java/com/android/dialer/shortcuts/res/values/strings.xml b/java/com/android/dialer/shortcuts/res/values/strings.xml index 1e2c87f12..5f14a8100 100644 --- a/java/com/android/dialer/shortcuts/res/values/strings.xml +++ b/java/com/android/dialer/shortcuts/res/values/strings.xml @@ -30,8 +30,8 @@ be found or doesn't have any phone numbers. [CHAR LIMIT=70] --> Contact no longer available. - - Cannot call without contact permissions. + + Cannot call without permissions. diff --git a/java/com/android/dialer/shortcuts/res/xml/shortcuts.xml b/java/com/android/dialer/shortcuts/res/xml/shortcuts.xml index 5e8f58d1f..49149e3e1 100644 --- a/java/com/android/dialer/shortcuts/res/xml/shortcuts.xml +++ b/java/com/android/dialer/shortcuts/res/xml/shortcuts.xml @@ -24,8 +24,6 @@ + android:data="content://com.android.contacts/contacts"/> diff --git a/java/com/android/dialer/simulator/SimulatorComponent.java b/java/com/android/dialer/simulator/SimulatorComponent.java new file mode 100644 index 000000000..a16592e34 --- /dev/null +++ b/java/com/android/dialer/simulator/SimulatorComponent.java @@ -0,0 +1,46 @@ +/* + * 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.simulator; + +import android.content.Context; +import dagger.Subcomponent; +import com.android.dialer.simulator.impl.SimulatorImpl; + +/** Subcomponent that can be used to access the simulator implementation. */ +public class SimulatorComponent { + private static SimulatorComponent instance; + private Simulator simulator; + + public Simulator getSimulator() { + if (simulator == null) { + simulator = new SimulatorImpl(); + } + return simulator; + } + + public static SimulatorComponent get(Context context) { + if (instance == null) { + instance = new SimulatorComponent(); + } + return instance; + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + SimulatorComponent simulatorComponent(); + } +} diff --git a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java deleted file mode 100644 index 591819819..000000000 --- a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorCallLog_CallEntry.java +++ /dev/null @@ -1,160 +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.simulator.impl; - -import android.support.annotation.NonNull; -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_SimulatorCallLog_CallEntry extends SimulatorCallLog.CallEntry { - - private final String number; - private final int type; - private final int presentation; - private final long timeMillis; - - private AutoValue_SimulatorCallLog_CallEntry( - String number, - int type, - int presentation, - long timeMillis) { - this.number = number; - this.type = type; - this.presentation = presentation; - this.timeMillis = timeMillis; - } - - @NonNull - @Override - String getNumber() { - return number; - } - - @Override - int getType() { - return type; - } - - @Override - int getPresentation() { - return presentation; - } - - @Override - long getTimeMillis() { - return timeMillis; - } - - @Override - public String toString() { - return "CallEntry{" - + "number=" + number + ", " - + "type=" + type + ", " - + "presentation=" + presentation + ", " - + "timeMillis=" + timeMillis - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof SimulatorCallLog.CallEntry) { - SimulatorCallLog.CallEntry that = (SimulatorCallLog.CallEntry) o; - return (this.number.equals(that.getNumber())) - && (this.type == that.getType()) - && (this.presentation == that.getPresentation()) - && (this.timeMillis == that.getTimeMillis()); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= this.number.hashCode(); - h *= 1000003; - h ^= this.type; - h *= 1000003; - h ^= this.presentation; - h *= 1000003; - h ^= (this.timeMillis >>> 32) ^ this.timeMillis; - return h; - } - - static final class Builder extends SimulatorCallLog.CallEntry.Builder { - private String number; - private Integer type; - private Integer presentation; - private Long timeMillis; - Builder() { - } - private Builder(SimulatorCallLog.CallEntry source) { - this.number = source.getNumber(); - this.type = source.getType(); - this.presentation = source.getPresentation(); - this.timeMillis = source.getTimeMillis(); - } - @Override - SimulatorCallLog.CallEntry.Builder setNumber(String number) { - this.number = number; - return this; - } - @Override - SimulatorCallLog.CallEntry.Builder setType(int type) { - this.type = type; - return this; - } - @Override - SimulatorCallLog.CallEntry.Builder setPresentation(int presentation) { - this.presentation = presentation; - return this; - } - @Override - SimulatorCallLog.CallEntry.Builder setTimeMillis(long timeMillis) { - this.timeMillis = timeMillis; - return this; - } - @Override - SimulatorCallLog.CallEntry build() { - String missing = ""; - if (this.number == null) { - missing += " number"; - } - if (this.type == null) { - missing += " type"; - } - if (this.presentation == null) { - missing += " presentation"; - } - if (this.timeMillis == null) { - missing += " timeMillis"; - } - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } - return new AutoValue_SimulatorCallLog_CallEntry( - this.number, - this.type, - this.presentation, - this.timeMillis); - } - } - -} diff --git a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java deleted file mode 100644 index 00295f359..000000000 --- a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorContacts_Contact.java +++ /dev/null @@ -1,231 +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.simulator.impl; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import java.io.ByteArrayOutputStream; -import java.util.List; -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_SimulatorContacts_Contact extends SimulatorContacts.Contact { - - private final String accountType; - private final String accountName; - private final String name; - private final boolean isStarred; - private final ByteArrayOutputStream photoStream; - private final List phoneNumbers; - private final List emails; - - private AutoValue_SimulatorContacts_Contact( - String accountType, - String accountName, - @Nullable String name, - boolean isStarred, - @Nullable ByteArrayOutputStream photoStream, - List phoneNumbers, - List emails) { - this.accountType = accountType; - this.accountName = accountName; - this.name = name; - this.isStarred = isStarred; - this.photoStream = photoStream; - this.phoneNumbers = phoneNumbers; - this.emails = emails; - } - - @NonNull - @Override - String getAccountType() { - return accountType; - } - - @NonNull - @Override - String getAccountName() { - return accountName; - } - - @Nullable - @Override - String getName() { - return name; - } - - @Override - boolean getIsStarred() { - return isStarred; - } - - @Nullable - @Override - ByteArrayOutputStream getPhotoStream() { - return photoStream; - } - - @NonNull - @Override - List getPhoneNumbers() { - return phoneNumbers; - } - - @NonNull - @Override - List getEmails() { - return emails; - } - - @Override - public String toString() { - return "Contact{" - + "accountType=" + accountType + ", " - + "accountName=" + accountName + ", " - + "name=" + name + ", " - + "isStarred=" + isStarred + ", " - + "photoStream=" + photoStream + ", " - + "phoneNumbers=" + phoneNumbers + ", " - + "emails=" + emails - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof SimulatorContacts.Contact) { - SimulatorContacts.Contact that = (SimulatorContacts.Contact) o; - return (this.accountType.equals(that.getAccountType())) - && (this.accountName.equals(that.getAccountName())) - && ((this.name == null) ? (that.getName() == null) : this.name.equals(that.getName())) - && (this.isStarred == that.getIsStarred()) - && ((this.photoStream == null) ? (that.getPhotoStream() == null) : this.photoStream.equals(that.getPhotoStream())) - && (this.phoneNumbers.equals(that.getPhoneNumbers())) - && (this.emails.equals(that.getEmails())); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= this.accountType.hashCode(); - h *= 1000003; - h ^= this.accountName.hashCode(); - h *= 1000003; - h ^= (name == null) ? 0 : this.name.hashCode(); - h *= 1000003; - h ^= this.isStarred ? 1231 : 1237; - h *= 1000003; - h ^= (photoStream == null) ? 0 : this.photoStream.hashCode(); - h *= 1000003; - h ^= this.phoneNumbers.hashCode(); - h *= 1000003; - h ^= this.emails.hashCode(); - return h; - } - - static final class Builder extends SimulatorContacts.Contact.Builder { - private String accountType; - private String accountName; - private String name; - private Boolean isStarred; - private ByteArrayOutputStream photoStream; - private List phoneNumbers; - private List emails; - Builder() { - } - private Builder(SimulatorContacts.Contact source) { - this.accountType = source.getAccountType(); - this.accountName = source.getAccountName(); - this.name = source.getName(); - this.isStarred = source.getIsStarred(); - this.photoStream = source.getPhotoStream(); - this.phoneNumbers = source.getPhoneNumbers(); - this.emails = source.getEmails(); - } - @Override - SimulatorContacts.Contact.Builder setAccountType(String accountType) { - this.accountType = accountType; - return this; - } - @Override - SimulatorContacts.Contact.Builder setAccountName(String accountName) { - this.accountName = accountName; - return this; - } - @Override - SimulatorContacts.Contact.Builder setName(@Nullable String name) { - this.name = name; - return this; - } - @Override - SimulatorContacts.Contact.Builder setIsStarred(boolean isStarred) { - this.isStarred = isStarred; - return this; - } - @Override - SimulatorContacts.Contact.Builder setPhotoStream(@Nullable ByteArrayOutputStream photoStream) { - this.photoStream = photoStream; - return this; - } - @Override - SimulatorContacts.Contact.Builder setPhoneNumbers(List phoneNumbers) { - this.phoneNumbers = phoneNumbers; - return this; - } - @Override - SimulatorContacts.Contact.Builder setEmails(List emails) { - this.emails = emails; - return this; - } - @Override - SimulatorContacts.Contact build() { - String missing = ""; - if (this.accountType == null) { - missing += " accountType"; - } - if (this.accountName == null) { - missing += " accountName"; - } - if (this.isStarred == null) { - missing += " isStarred"; - } - if (this.phoneNumbers == null) { - missing += " phoneNumbers"; - } - if (this.emails == null) { - missing += " emails"; - } - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } - return new AutoValue_SimulatorContacts_Contact( - this.accountType, - this.accountName, - this.name, - this.isStarred, - this.photoStream, - this.phoneNumbers, - this.emails); - } - } - -} diff --git a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java b/java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java deleted file mode 100644 index 58934801c..000000000 --- a/java/com/android/dialer/simulator/impl/AutoValue_SimulatorVoicemail_Voicemail.java +++ /dev/null @@ -1,184 +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.simulator.impl; - -import android.support.annotation.NonNull; -import javax.annotation.Generated; - -@Generated("com.google.auto.value.processor.AutoValueProcessor") - final class AutoValue_SimulatorVoicemail_Voicemail extends SimulatorVoicemail.Voicemail { - - private final String phoneNumber; - private final String transcription; - private final long durationSeconds; - private final long timeMillis; - private final boolean isRead; - - private AutoValue_SimulatorVoicemail_Voicemail( - String phoneNumber, - String transcription, - long durationSeconds, - long timeMillis, - boolean isRead) { - this.phoneNumber = phoneNumber; - this.transcription = transcription; - this.durationSeconds = durationSeconds; - this.timeMillis = timeMillis; - this.isRead = isRead; - } - - @NonNull - @Override - String getPhoneNumber() { - return phoneNumber; - } - - @NonNull - @Override - String getTranscription() { - return transcription; - } - - @Override - long getDurationSeconds() { - return durationSeconds; - } - - @Override - long getTimeMillis() { - return timeMillis; - } - - @Override - boolean getIsRead() { - return isRead; - } - - @Override - public String toString() { - return "Voicemail{" - + "phoneNumber=" + phoneNumber + ", " - + "transcription=" + transcription + ", " - + "durationSeconds=" + durationSeconds + ", " - + "timeMillis=" + timeMillis + ", " - + "isRead=" + isRead - + "}"; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof SimulatorVoicemail.Voicemail) { - SimulatorVoicemail.Voicemail that = (SimulatorVoicemail.Voicemail) o; - return (this.phoneNumber.equals(that.getPhoneNumber())) - && (this.transcription.equals(that.getTranscription())) - && (this.durationSeconds == that.getDurationSeconds()) - && (this.timeMillis == that.getTimeMillis()) - && (this.isRead == that.getIsRead()); - } - return false; - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= this.phoneNumber.hashCode(); - h *= 1000003; - h ^= this.transcription.hashCode(); - h *= 1000003; - h ^= (this.durationSeconds >>> 32) ^ this.durationSeconds; - h *= 1000003; - h ^= (this.timeMillis >>> 32) ^ this.timeMillis; - h *= 1000003; - h ^= this.isRead ? 1231 : 1237; - return h; - } - - static final class Builder extends SimulatorVoicemail.Voicemail.Builder { - private String phoneNumber; - private String transcription; - private Long durationSeconds; - private Long timeMillis; - private Boolean isRead; - Builder() { - } - private Builder(SimulatorVoicemail.Voicemail source) { - this.phoneNumber = source.getPhoneNumber(); - this.transcription = source.getTranscription(); - this.durationSeconds = source.getDurationSeconds(); - this.timeMillis = source.getTimeMillis(); - this.isRead = source.getIsRead(); - } - @Override - SimulatorVoicemail.Voicemail.Builder setPhoneNumber(String phoneNumber) { - this.phoneNumber = phoneNumber; - return this; - } - @Override - SimulatorVoicemail.Voicemail.Builder setTranscription(String transcription) { - this.transcription = transcription; - return this; - } - @Override - SimulatorVoicemail.Voicemail.Builder setDurationSeconds(long durationSeconds) { - this.durationSeconds = durationSeconds; - return this; - } - @Override - SimulatorVoicemail.Voicemail.Builder setTimeMillis(long timeMillis) { - this.timeMillis = timeMillis; - return this; - } - @Override - SimulatorVoicemail.Voicemail.Builder setIsRead(boolean isRead) { - this.isRead = isRead; - return this; - } - @Override - SimulatorVoicemail.Voicemail build() { - String missing = ""; - if (this.phoneNumber == null) { - missing += " phoneNumber"; - } - if (this.transcription == null) { - missing += " transcription"; - } - if (this.durationSeconds == null) { - missing += " durationSeconds"; - } - if (this.timeMillis == null) { - missing += " timeMillis"; - } - if (this.isRead == null) { - missing += " isRead"; - } - if (!missing.isEmpty()) { - throw new IllegalStateException("Missing required properties:" + missing); - } - return new AutoValue_SimulatorVoicemail_Voicemail( - this.phoneNumber, - this.transcription, - this.durationSeconds, - this.timeMillis, - this.isRead); - } - } - -} diff --git a/java/com/android/dialer/simulator/impl/SimulatorCallLog.java b/java/com/android/dialer/simulator/impl/SimulatorCallLog.java index 9ace047d0..f127d5603 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorCallLog.java +++ b/java/com/android/dialer/simulator/impl/SimulatorCallLog.java @@ -26,7 +26,7 @@ import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; import com.android.dialer.common.Assert; - +import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.concurrent.TimeUnit; @@ -96,7 +96,7 @@ final class SimulatorCallLog { } } - + @AutoValue abstract static class CallEntry { @NonNull abstract String getNumber(); @@ -121,7 +121,7 @@ final class SimulatorCallLog { return values; } - + @AutoValue.Builder abstract static class Builder { abstract Builder setNumber(@NonNull String number); diff --git a/java/com/android/dialer/simulator/impl/SimulatorContacts.java b/java/com/android/dialer/simulator/impl/SimulatorContacts.java index 89315094a..c5e25b357 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorContacts.java +++ b/java/com/android/dialer/simulator/impl/SimulatorContacts.java @@ -31,7 +31,7 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.text.TextUtils; import com.android.dialer.common.Assert; - +import com.google.auto.value.AutoValue; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; @@ -190,7 +190,7 @@ final class SimulatorContacts { } } - + @AutoValue abstract static class Contact { @NonNull abstract String getAccountType(); @@ -221,7 +221,7 @@ final class SimulatorContacts { .setEmails(new ArrayList<>()); } - + @AutoValue.Builder abstract static class Builder { @NonNull private final List phoneNumbers = new ArrayList<>(); @NonNull private final List emails = new ArrayList<>(); diff --git a/java/com/android/dialer/simulator/impl/SimulatorImpl.java b/java/com/android/dialer/simulator/impl/SimulatorImpl.java new file mode 100644 index 000000000..9c6826940 --- /dev/null +++ b/java/com/android/dialer/simulator/impl/SimulatorImpl.java @@ -0,0 +1,40 @@ +/* + * 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.simulator.impl; + +import android.content.Context; +import android.view.ActionProvider; +import com.android.dialer.buildtype.BuildType; +import com.android.dialer.common.LogUtil; +import com.android.dialer.simulator.Simulator; +import javax.inject.Inject; + +/** The entry point for the simulator feature. */ +final public class SimulatorImpl implements Simulator { + @Inject + public SimulatorImpl() {} + + @Override + public boolean shouldShow() { + return BuildType.get() == BuildType.BUGFOOD || LogUtil.isDebugEnabled(); + } + + @Override + public ActionProvider getActionProvider(Context context) { + return new SimulatorActionProvider(context); + } +} diff --git a/java/com/android/dialer/simulator/impl/SimulatorModule.java b/java/com/android/dialer/simulator/impl/SimulatorModule.java index 0f8ad3954..c0cca271b 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorModule.java +++ b/java/com/android/dialer/simulator/impl/SimulatorModule.java @@ -16,19 +16,15 @@ package com.android.dialer.simulator.impl; -import android.content.Context; -import android.view.ActionProvider; import com.android.dialer.simulator.Simulator; +import dagger.Binds; +import dagger.Module; +import javax.inject.Singleton; -/** The entry point for the simulator module. */ -public final class SimulatorModule implements Simulator { - @Override - public boolean shouldShow() { - return true; - } - - @Override - public ActionProvider getActionProvider(Context context) { - return new SimulatorActionProvider(context); - } +/** This module provides an instance of the simulator. */ +@Module +public abstract class SimulatorModule { + @Binds + @Singleton + public abstract Simulator bindsSimulator(SimulatorImpl simulator); } diff --git a/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java b/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java index ffb9191dc..04de201ae 100644 --- a/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java +++ b/java/com/android/dialer/simulator/impl/SimulatorVoicemail.java @@ -26,7 +26,7 @@ import android.support.annotation.WorkerThread; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; import com.android.dialer.common.Assert; - +import com.google.auto.value.AutoValue; import java.util.concurrent.TimeUnit; /** Populates the device database with voicemail entries. */ @@ -105,7 +105,7 @@ final class SimulatorVoicemail { context.getContentResolver().insert(Status.buildSourceUri(context.getPackageName()), values); } - + @AutoValue abstract static class Voicemail { @NonNull abstract String getPhoneNumber(); @@ -134,7 +134,7 @@ final class SimulatorVoicemail { return values; } - + @AutoValue.Builder abstract static class Builder { abstract Builder setPhoneNumber(@NonNull String phoneNumber); diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java index a11e7f77a..87ddda58b 100644 --- a/java/com/android/dialer/telecom/TelecomUtil.java +++ b/java/com/android/dialer/telecom/TelecomUtil.java @@ -23,12 +23,13 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.provider.CallLog.Calls; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.v4.content.ContextCompat; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.text.TextUtils; -import android.util.Log; +import com.android.dialer.common.LogUtil; import java.util.ArrayList; import java.util.List; @@ -41,6 +42,8 @@ public class TelecomUtil { private static final String TAG = "TelecomUtil"; private static boolean sWarningLogged = false; + private static Boolean isDefaultDialerForTesting; + private static Boolean hasPermissionForTesting; public static void showInCallScreen(Context context, boolean showDialpad) { if (hasReadPhoneStatePermission(context)) { @@ -48,7 +51,7 @@ public class TelecomUtil { getTelecomManager(context).showInCallScreen(showDialpad); } catch (SecurityException e) { // Just in case - Log.w(TAG, "TelecomManager.showInCallScreen called without permission."); + LogUtil.w(TAG, "TelecomManager.showInCallScreen called without permission."); } } } @@ -59,7 +62,7 @@ public class TelecomUtil { getTelecomManager(context).silenceRinger(); } catch (SecurityException e) { // Just in case - Log.w(TAG, "TelecomManager.silenceRinger called without permission."); + LogUtil.w(TAG, "TelecomManager.silenceRinger called without permission."); } } } @@ -69,7 +72,7 @@ public class TelecomUtil { try { getTelecomManager(context).cancelMissedCallsNotification(); } catch (SecurityException e) { - Log.w(TAG, "TelecomManager.cancelMissedCalls called without permission."); + LogUtil.w(TAG, "TelecomManager.cancelMissedCalls called without permission."); } } } @@ -79,7 +82,7 @@ public class TelecomUtil { try { return getTelecomManager(context).getAdnUriForPhoneAccount(handle); } catch (SecurityException e) { - Log.w(TAG, "TelecomManager.getAdnUriForPhoneAccount called without permission."); + LogUtil.w(TAG, "TelecomManager.getAdnUriForPhoneAccount called without permission."); } } return null; @@ -95,7 +98,7 @@ public class TelecomUtil { return getTelecomManager(context).handleMmi(dialString, handle); } } catch (SecurityException e) { - Log.w(TAG, "TelecomManager.handleMmi called without permission."); + LogUtil.w(TAG, "TelecomManager.handleMmi called without permission."); } } return false; @@ -186,11 +189,17 @@ public class TelecomUtil { } private static boolean hasPermission(Context context, String permission) { + if (hasPermissionForTesting != null) { + return hasPermissionForTesting; + } return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; } public static boolean isDefaultDialer(Context context) { + if (isDefaultDialerForTesting != null) { + return isDefaultDialerForTesting; + } final boolean result = TextUtils.equals( context.getPackageName(), getTelecomManager(context).getDefaultDialerPackage()); @@ -199,7 +208,7 @@ public class TelecomUtil { } else { if (!sWarningLogged) { // Log only once to prevent spam. - Log.w(TAG, "Dialer is not currently set to be default dialer"); + LogUtil.w(TAG, "Dialer is not currently set to be default dialer"); sWarningLogged = true; } } @@ -209,4 +218,14 @@ public class TelecomUtil { private static TelecomManager getTelecomManager(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public static void setIsDefaultDialerForTesting(Boolean defaultDialer) { + isDefaultDialerForTesting = defaultDialer; + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public static void setHasPermissionForTesting(Boolean hasPermission) { + hasPermissionForTesting = hasPermission; + } } diff --git a/java/com/android/dialer/theme/res/drawable-hdpi/ic_block_24dp.png b/java/com/android/dialer/theme/res/drawable-hdpi/ic_block_24dp.png new file mode 100644 index 000000000..2ccc89d24 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-hdpi/ic_block_24dp.png differ diff --git a/java/com/android/dialer/theme/res/drawable-hdpi/ic_call_arrow.png b/java/com/android/dialer/theme/res/drawable-hdpi/ic_call_arrow.png new file mode 100644 index 000000000..14a33e39f Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-hdpi/ic_call_arrow.png differ diff --git a/java/com/android/dialer/theme/res/drawable-mdpi/ic_block_24dp.png b/java/com/android/dialer/theme/res/drawable-mdpi/ic_block_24dp.png new file mode 100644 index 000000000..ec1b33f0e Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-mdpi/ic_block_24dp.png differ diff --git a/java/com/android/dialer/theme/res/drawable-mdpi/ic_call_arrow.png b/java/com/android/dialer/theme/res/drawable-mdpi/ic_call_arrow.png new file mode 100644 index 000000000..169cf2934 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-mdpi/ic_call_arrow.png differ diff --git a/java/com/android/dialer/theme/res/drawable-xhdpi/ic_block_24dp.png b/java/com/android/dialer/theme/res/drawable-xhdpi/ic_block_24dp.png new file mode 100644 index 000000000..7aba97b65 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-xhdpi/ic_block_24dp.png differ diff --git a/java/com/android/dialer/theme/res/drawable-xhdpi/ic_call_arrow.png b/java/com/android/dialer/theme/res/drawable-xhdpi/ic_call_arrow.png new file mode 100644 index 000000000..6f1366018 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-xhdpi/ic_call_arrow.png differ diff --git a/java/com/android/dialer/theme/res/drawable-xxhdpi/ic_block_24dp.png b/java/com/android/dialer/theme/res/drawable-xxhdpi/ic_block_24dp.png new file mode 100644 index 000000000..fddfa54b8 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-xxhdpi/ic_block_24dp.png differ diff --git a/java/com/android/dialer/theme/res/drawable-xxhdpi/ic_call_arrow.png b/java/com/android/dialer/theme/res/drawable-xxhdpi/ic_call_arrow.png new file mode 100644 index 000000000..0364ee015 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-xxhdpi/ic_call_arrow.png differ diff --git a/java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_block_24dp.png b/java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_block_24dp.png new file mode 100644 index 000000000..0378d1bed Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_block_24dp.png differ diff --git a/java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_call_arrow.png b/java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_call_arrow.png new file mode 100644 index 000000000..8243c2536 Binary files /dev/null and b/java/com/android/dialer/theme/res/drawable-xxxhdpi/ic_call_arrow.png differ diff --git a/java/com/android/dialer/theme/res/values/dimens.xml b/java/com/android/dialer/theme/res/values/dimens.xml index 2d11ecc84..fa750c625 100644 --- a/java/com/android/dialer/theme/res/values/dimens.xml +++ b/java/com/android/dialer/theme/res/values/dimens.xml @@ -25,4 +25,9 @@ 107dp 72dp + + 20sp + 14sp + + 4dp diff --git a/java/com/android/dialer/theme/res/values/styles.xml b/java/com/android/dialer/theme/res/values/styles.xml index ac94d0687..b5e89ff48 100644 --- a/java/com/android/dialer/theme/res/values/styles.xml +++ b/java/com/android/dialer/theme/res/values/styles.xml @@ -53,4 +53,15 @@ @color/actionbar_background_color @color/actionbar_background_color + + + + diff --git a/java/com/android/dialer/util/AndroidManifest.xml b/java/com/android/dialer/util/AndroidManifest.xml index 499df9b4e..ba22c1781 100644 --- a/java/com/android/dialer/util/AndroidManifest.xml +++ b/java/com/android/dialer/util/AndroidManifest.xml @@ -1,3 +1,19 @@ + + diff --git a/java/com/android/dialer/util/PermissionsUtil.java b/java/com/android/dialer/util/PermissionsUtil.java index 70b96dfe1..5741e734a 100644 --- a/java/com/android/dialer/util/PermissionsUtil.java +++ b/java/com/android/dialer/util/PermissionsUtil.java @@ -47,6 +47,10 @@ public class PermissionsUtil { return hasPermission(context, permission.CAMERA); } + public static boolean hasMicrophonePermissions(Context context) { + return hasPermission(context, permission.RECORD_AUDIO); + } + public static boolean hasPermission(Context context, String permission) { return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; diff --git a/java/com/android/dialer/util/SettingsUtil.java b/java/com/android/dialer/util/SettingsUtil.java index c61c09b6c..5043c3d56 100644 --- a/java/com/android/dialer/util/SettingsUtil.java +++ b/java/com/android/dialer/util/SettingsUtil.java @@ -69,6 +69,15 @@ public class SettingsUtil { } } } + getRingtoneName(context, handler, ringtoneUri, msg, defaultRingtone); + } + + public static void getRingtoneName(Context context, Handler handler, Uri ringtoneUri, int msg) { + getRingtoneName(context, handler, ringtoneUri, msg, false); + } + + public static void getRingtoneName( + Context context, Handler handler, Uri ringtoneUri, int msg, boolean defaultRingtone) { CharSequence summary = context.getString(R.string.ringtone_unknown); // Is it a silent ringtone? if (ringtoneUri == null) { diff --git a/java/com/android/dialer/util/ViewUtil.java b/java/com/android/dialer/util/ViewUtil.java index de08e41a7..81a32f985 100644 --- a/java/com/android/dialer/util/ViewUtil.java +++ b/java/com/android/dialer/util/ViewUtil.java @@ -27,6 +27,7 @@ import android.text.TextUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.ViewTreeObserver.OnPreDrawListener; import android.widget.TextView; import java.util.Locale; @@ -113,6 +114,18 @@ public class ViewUtil { }); } + public static void doOnGlobalLayout(@NonNull final View view, final ViewRunnable runnable) { + view.getViewTreeObserver() + .addOnGlobalLayoutListener( + new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + runnable.run(view); + } + }); + } + /** * Returns {@code true} if animations should be disabled. * diff --git a/java/com/android/dialer/widget/MessageFragment.java b/java/com/android/dialer/widget/MessageFragment.java new file mode 100644 index 000000000..ab47f2463 --- /dev/null +++ b/java/com/android/dialer/widget/MessageFragment.java @@ -0,0 +1,172 @@ +/* + * 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.widget; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.dialer.common.Assert; +import com.android.dialer.common.FragmentUtils; + +/** Fragment used to compose call with message fragment. */ +public class MessageFragment extends Fragment implements OnClickListener, TextWatcher { + private static final String CHAR_LIMIT_KEY = "char_limit"; + private static final String SHOW_SEND_ICON_KEY = "show_send_icon"; + private static final String MESSAGE_LIST_KEY = "message_list"; + + public static final int NO_CHAR_LIMIT = -1; + + private EditText customMessage; + private ImageView sendMessage; + private TextView remainingChar; + private int charLimit; + + private static MessageFragment newInstance(Builder builder) { + MessageFragment fragment = new MessageFragment(); + Bundle args = new Bundle(); + args.putInt(CHAR_LIMIT_KEY, builder.charLimit); + args.putBoolean(SHOW_SEND_ICON_KEY, builder.showSendIcon); + args.putStringArray(MESSAGE_LIST_KEY, builder.messages); + fragment.setArguments(args); + return fragment; + } + + @Nullable + public String getMessage() { + return customMessage == null ? null : customMessage.getText().toString(); + } + + @Nullable + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_message, container, false); + + sendMessage = (ImageView) view.findViewById(R.id.send_message); + if (getArguments().getBoolean(SHOW_SEND_ICON_KEY, false)) { + sendMessage.setVisibility(View.VISIBLE); + sendMessage.setEnabled(false); + sendMessage.setOnClickListener(this); + } + + customMessage = (EditText) view.findViewById(R.id.custom_message); + customMessage.addTextChangedListener(this); + charLimit = getArguments().getInt(CHAR_LIMIT_KEY, NO_CHAR_LIMIT); + if (charLimit != NO_CHAR_LIMIT) { + remainingChar = (TextView) view.findViewById(R.id.remaining_characters); + remainingChar.setVisibility(View.VISIBLE); + remainingChar = (TextView) view.findViewById(R.id.remaining_characters); + remainingChar.setText("" + charLimit); + customMessage.setFilters(new InputFilter[] {new InputFilter.LengthFilter(charLimit)}); + } + + LinearLayout messageContainer = (LinearLayout) view.findViewById(R.id.message_container); + for (String message : getArguments().getStringArray(MESSAGE_LIST_KEY)) { + TextView textView = (TextView) inflater.inflate(R.layout.selectable_text_view, null); + textView.setOnClickListener(this); + textView.setText(message); + messageContainer.addView(textView); + } + return view; + } + + @Override + public void onClick(View view) { + if (view == sendMessage) { + getListener().onMessageFragmentSendMessage(customMessage.getText().toString()); + } else if (view.getId() == R.id.selectable_text_view) { + customMessage.setText(((TextView) view).getText()); + customMessage.setSelection(customMessage.getText().length()); + } else { + Assert.fail("Unknown view clicked"); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + sendMessage.setEnabled(s.length() > 0); + } + + @Override + public void afterTextChanged(Editable s) { + if (charLimit != NO_CHAR_LIMIT) { + remainingChar.setText("" + (charLimit - s.length())); + } + getListener().onMessageFragmentAfterTextChange(s.toString()); + } + + private Listener getListener() { + return FragmentUtils.getParentUnsafe(this, Listener.class); + } + + public static Builder builder() { + return new Builder(); + } + + /** Builder for {@link MessageFragment}. */ + public static class Builder { + private String[] messages; + private boolean showSendIcon; + private int charLimit = NO_CHAR_LIMIT; + + /** + * @throws NullPointerException if message is null + * @throws IllegalArgumentException if messages.length is outside the range [1,3]. + */ + public Builder setMessages(String... messages) { + // Since we only allow up to 3 messages, crash if more are set. + Assert.checkArgument(messages.length > 0 && messages.length <= 3); + this.messages = messages; + return this; + } + + public Builder showSendIcon() { + showSendIcon = true; + return this; + } + + public Builder setCharLimit(int charLimit) { + this.charLimit = charLimit; + return this; + } + + public MessageFragment build() { + return MessageFragment.newInstance(this); + } + } + + /** Interface for parent activity to implement to listen for important events. */ + public interface Listener { + void onMessageFragmentSendMessage(String message); + + void onMessageFragmentAfterTextChange(String message); + } +} diff --git a/java/com/android/dialer/widget/res/color/dialer_tint_state.xml b/java/com/android/dialer/widget/res/color/dialer_tint_state.xml new file mode 100644 index 000000000..c29f334ac --- /dev/null +++ b/java/com/android/dialer/widget/res/color/dialer_tint_state.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/widget/res/layout/fragment_message.xml b/java/com/android/dialer/widget/res/layout/fragment_message.xml new file mode 100644 index 000000000..f09c54f57 --- /dev/null +++ b/java/com/android/dialer/widget/res/layout/fragment_message.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/widget/res/layout/selectable_text_view.xml b/java/com/android/dialer/widget/res/layout/selectable_text_view.xml new file mode 100644 index 000000000..3d120d13d --- /dev/null +++ b/java/com/android/dialer/widget/res/layout/selectable_text_view.xml @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/java/com/android/dialer/widget/res/values/dimens.xml b/java/com/android/dialer/widget/res/values/dimens.xml new file mode 100644 index 000000000..6c4ea604f --- /dev/null +++ b/java/com/android/dialer/widget/res/values/dimens.xml @@ -0,0 +1,23 @@ + + + + + 16sp + 16dp + 12sp + 1dp + \ No newline at end of file diff --git a/java/com/android/dialer/widget/res/values/strings.xml b/java/com/android/dialer/widget/res/values/strings.xml new file mode 100644 index 000000000..6904c2de1 --- /dev/null +++ b/java/com/android/dialer/widget/res/values/strings.xml @@ -0,0 +1,5 @@ + + + + Write a custom message + \ No newline at end of file -- cgit v1.2.3